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,405 @@
|
|
1
|
+
"""
|
2
|
+
Configuration Converter for security framework integration.
|
3
|
+
|
4
|
+
This module provides utilities to convert between mcp_proxy_adapter configuration
|
5
|
+
format and mcp_security_framework configuration format, ensuring backward compatibility.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import json
|
9
|
+
import logging
|
10
|
+
from typing import Dict, Any, Optional
|
11
|
+
from pathlib import Path
|
12
|
+
|
13
|
+
from mcp_proxy_adapter.core.logging import logger
|
14
|
+
|
15
|
+
|
16
|
+
class ConfigConverter:
|
17
|
+
"""
|
18
|
+
Converter for configuration formats.
|
19
|
+
|
20
|
+
Provides methods to convert between mcp_proxy_adapter configuration
|
21
|
+
and mcp_security_framework configuration formats.
|
22
|
+
"""
|
23
|
+
|
24
|
+
@staticmethod
|
25
|
+
def to_security_framework_config(mcp_config: Dict[str, Any]) -> Dict[str, Any]:
|
26
|
+
"""
|
27
|
+
Convert mcp_proxy_adapter configuration to SecurityConfig format.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
mcp_config: mcp_proxy_adapter configuration dictionary
|
31
|
+
|
32
|
+
Returns:
|
33
|
+
SecurityConfig compatible dictionary
|
34
|
+
"""
|
35
|
+
try:
|
36
|
+
# Start with default security framework config
|
37
|
+
security_config = {
|
38
|
+
"auth": {
|
39
|
+
"enabled": True,
|
40
|
+
"methods": ["api_key"],
|
41
|
+
"api_keys": {},
|
42
|
+
"jwt_secret": "",
|
43
|
+
"jwt_algorithm": "HS256"
|
44
|
+
},
|
45
|
+
"ssl": {
|
46
|
+
"enabled": False,
|
47
|
+
"cert_file": None,
|
48
|
+
"key_file": None,
|
49
|
+
"ca_cert": None,
|
50
|
+
"min_tls_version": "TLSv1.2",
|
51
|
+
"verify_client": False,
|
52
|
+
"client_cert_required": False
|
53
|
+
},
|
54
|
+
"permissions": {
|
55
|
+
"enabled": True,
|
56
|
+
"roles_file": "roles.json",
|
57
|
+
"default_role": "user",
|
58
|
+
"deny_by_default": True
|
59
|
+
},
|
60
|
+
"rate_limit": {
|
61
|
+
"enabled": True,
|
62
|
+
"requests_per_minute": 60,
|
63
|
+
"requests_per_hour": 1000,
|
64
|
+
"burst_limit": 10,
|
65
|
+
"by_ip": True,
|
66
|
+
"by_user": True
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
# Convert from security section if exists
|
71
|
+
if "security" in mcp_config:
|
72
|
+
security_section = mcp_config["security"]
|
73
|
+
|
74
|
+
# Convert auth config
|
75
|
+
if "auth" in security_section:
|
76
|
+
auth_config = security_section["auth"]
|
77
|
+
security_config["auth"].update({
|
78
|
+
"enabled": auth_config.get("enabled", True),
|
79
|
+
"methods": auth_config.get("methods", ["api_key"]),
|
80
|
+
"api_keys": auth_config.get("api_keys", {}),
|
81
|
+
"jwt_secret": auth_config.get("jwt_secret", ""),
|
82
|
+
"jwt_algorithm": auth_config.get("jwt_algorithm", "HS256")
|
83
|
+
})
|
84
|
+
|
85
|
+
# Convert SSL config
|
86
|
+
if "ssl" in security_section:
|
87
|
+
ssl_config = security_section["ssl"]
|
88
|
+
security_config["ssl"].update({
|
89
|
+
"enabled": ssl_config.get("enabled", False),
|
90
|
+
"cert_file": ssl_config.get("cert_file"),
|
91
|
+
"key_file": ssl_config.get("key_file"),
|
92
|
+
"ca_cert": ssl_config.get("ca_cert"),
|
93
|
+
"min_tls_version": ssl_config.get("min_tls_version", "TLSv1.2"),
|
94
|
+
"verify_client": ssl_config.get("verify_client", False),
|
95
|
+
"client_cert_required": ssl_config.get("client_cert_required", False)
|
96
|
+
})
|
97
|
+
|
98
|
+
# Convert permissions config
|
99
|
+
if "permissions" in security_section:
|
100
|
+
permissions_config = security_section["permissions"]
|
101
|
+
security_config["permissions"].update({
|
102
|
+
"enabled": permissions_config.get("enabled", True),
|
103
|
+
"roles_file": permissions_config.get("roles_file", "roles.json"),
|
104
|
+
"default_role": permissions_config.get("default_role", "user"),
|
105
|
+
"deny_by_default": permissions_config.get("deny_by_default", True)
|
106
|
+
})
|
107
|
+
|
108
|
+
# Convert rate limit config
|
109
|
+
if "rate_limit" in security_section:
|
110
|
+
rate_limit_config = security_section["rate_limit"]
|
111
|
+
security_config["rate_limit"].update({
|
112
|
+
"enabled": rate_limit_config.get("enabled", True),
|
113
|
+
"requests_per_minute": rate_limit_config.get("requests_per_minute", 60),
|
114
|
+
"requests_per_hour": rate_limit_config.get("requests_per_hour", 1000),
|
115
|
+
"burst_limit": rate_limit_config.get("burst_limit", 10),
|
116
|
+
"by_ip": rate_limit_config.get("by_ip", True),
|
117
|
+
"by_user": rate_limit_config.get("by_user", True)
|
118
|
+
})
|
119
|
+
|
120
|
+
# Convert from legacy SSL config if security section doesn't exist
|
121
|
+
elif "ssl" in mcp_config:
|
122
|
+
ssl_config = mcp_config["ssl"]
|
123
|
+
security_config["ssl"].update({
|
124
|
+
"enabled": ssl_config.get("enabled", False),
|
125
|
+
"cert_file": ssl_config.get("cert_file"),
|
126
|
+
"key_file": ssl_config.get("key_file"),
|
127
|
+
"ca_cert": ssl_config.get("ca_cert"),
|
128
|
+
"min_tls_version": ssl_config.get("min_tls_version", "TLSv1.2"),
|
129
|
+
"verify_client": ssl_config.get("verify_client", False),
|
130
|
+
"client_cert_required": ssl_config.get("client_cert_required", False)
|
131
|
+
})
|
132
|
+
|
133
|
+
# Extract API keys from legacy SSL config
|
134
|
+
if "api_keys" in ssl_config:
|
135
|
+
security_config["auth"]["api_keys"] = ssl_config["api_keys"]
|
136
|
+
|
137
|
+
# Convert from legacy roles config
|
138
|
+
if "roles" in mcp_config:
|
139
|
+
roles_config = mcp_config["roles"]
|
140
|
+
security_config["permissions"].update({
|
141
|
+
"enabled": roles_config.get("enabled", True),
|
142
|
+
"roles_file": roles_config.get("config_file", "roles.json"),
|
143
|
+
"default_role": "user",
|
144
|
+
"deny_by_default": roles_config.get("default_policy", {}).get("deny_by_default", True)
|
145
|
+
})
|
146
|
+
|
147
|
+
logger.info("Configuration converted to security framework format successfully")
|
148
|
+
return security_config
|
149
|
+
|
150
|
+
except Exception as e:
|
151
|
+
logger.error(f"Failed to convert configuration to security framework format: {e}")
|
152
|
+
return ConfigConverter._get_default_security_config()
|
153
|
+
|
154
|
+
@staticmethod
|
155
|
+
def from_security_framework_config(security_config: Dict[str, Any]) -> Dict[str, Any]:
|
156
|
+
"""
|
157
|
+
Convert SecurityConfig format to mcp_proxy_adapter configuration.
|
158
|
+
|
159
|
+
Args:
|
160
|
+
security_config: SecurityConfig compatible dictionary
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
mcp_proxy_adapter configuration dictionary
|
164
|
+
"""
|
165
|
+
try:
|
166
|
+
mcp_config = {
|
167
|
+
"security": {
|
168
|
+
"framework": "mcp_security_framework",
|
169
|
+
"enabled": True
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
# Convert auth config
|
174
|
+
if "auth" in security_config:
|
175
|
+
auth_config = security_config["auth"]
|
176
|
+
mcp_config["security"]["auth"] = {
|
177
|
+
"enabled": auth_config.get("enabled", True),
|
178
|
+
"methods": auth_config.get("methods", ["api_key"]),
|
179
|
+
"api_keys": auth_config.get("api_keys", {}),
|
180
|
+
"jwt_secret": auth_config.get("jwt_secret", ""),
|
181
|
+
"jwt_algorithm": auth_config.get("jwt_algorithm", "HS256")
|
182
|
+
}
|
183
|
+
|
184
|
+
# Convert SSL config
|
185
|
+
if "ssl" in security_config:
|
186
|
+
ssl_config = security_config["ssl"]
|
187
|
+
mcp_config["security"]["ssl"] = {
|
188
|
+
"enabled": ssl_config.get("enabled", False),
|
189
|
+
"cert_file": ssl_config.get("cert_file"),
|
190
|
+
"key_file": ssl_config.get("key_file"),
|
191
|
+
"ca_cert": ssl_config.get("ca_cert"),
|
192
|
+
"min_tls_version": ssl_config.get("min_tls_version", "TLSv1.2"),
|
193
|
+
"verify_client": ssl_config.get("verify_client", False),
|
194
|
+
"client_cert_required": ssl_config.get("client_cert_required", False)
|
195
|
+
}
|
196
|
+
|
197
|
+
# Convert permissions config
|
198
|
+
if "permissions" in security_config:
|
199
|
+
permissions_config = security_config["permissions"]
|
200
|
+
mcp_config["security"]["permissions"] = {
|
201
|
+
"enabled": permissions_config.get("enabled", True),
|
202
|
+
"roles_file": permissions_config.get("roles_file", "roles.json"),
|
203
|
+
"default_role": permissions_config.get("default_role", "user"),
|
204
|
+
"deny_by_default": permissions_config.get("deny_by_default", True)
|
205
|
+
}
|
206
|
+
|
207
|
+
# Convert rate limit config
|
208
|
+
if "rate_limit" in security_config:
|
209
|
+
rate_limit_config = security_config["rate_limit"]
|
210
|
+
mcp_config["security"]["rate_limit"] = {
|
211
|
+
"enabled": rate_limit_config.get("enabled", True),
|
212
|
+
"requests_per_minute": rate_limit_config.get("requests_per_minute", 60),
|
213
|
+
"requests_per_hour": rate_limit_config.get("requests_per_hour", 1000),
|
214
|
+
"burst_limit": rate_limit_config.get("burst_limit", 10),
|
215
|
+
"by_ip": rate_limit_config.get("by_ip", True),
|
216
|
+
"by_user": rate_limit_config.get("by_user", True)
|
217
|
+
}
|
218
|
+
|
219
|
+
logger.info("Configuration converted from security framework format successfully")
|
220
|
+
return mcp_config
|
221
|
+
|
222
|
+
except Exception as e:
|
223
|
+
logger.error(f"Failed to convert configuration from security framework format: {e}")
|
224
|
+
return ConfigConverter._get_default_mcp_config()
|
225
|
+
|
226
|
+
@staticmethod
|
227
|
+
def migrate_legacy_config(config_path: str, output_path: Optional[str] = None) -> bool:
|
228
|
+
"""
|
229
|
+
Migrate legacy configuration to new security framework format.
|
230
|
+
|
231
|
+
Args:
|
232
|
+
config_path: Path to legacy configuration file
|
233
|
+
output_path: Path to output migrated configuration file (optional)
|
234
|
+
|
235
|
+
Returns:
|
236
|
+
True if migration successful, False otherwise
|
237
|
+
"""
|
238
|
+
try:
|
239
|
+
# Read legacy configuration
|
240
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
241
|
+
legacy_config = json.load(f)
|
242
|
+
|
243
|
+
# Convert to new format
|
244
|
+
new_config = ConfigConverter.to_security_framework_config(legacy_config)
|
245
|
+
|
246
|
+
# Add security section to legacy config
|
247
|
+
legacy_config["security"] = new_config
|
248
|
+
|
249
|
+
# Determine output path
|
250
|
+
if output_path is None:
|
251
|
+
output_path = config_path.replace('.json', '_migrated.json')
|
252
|
+
|
253
|
+
# Write migrated configuration
|
254
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
255
|
+
json.dump(legacy_config, f, indent=2, ensure_ascii=False)
|
256
|
+
|
257
|
+
logger.info(f"Configuration migrated successfully to {output_path}")
|
258
|
+
return True
|
259
|
+
|
260
|
+
except Exception as e:
|
261
|
+
logger.error(f"Failed to migrate configuration: {e}")
|
262
|
+
return False
|
263
|
+
|
264
|
+
@staticmethod
|
265
|
+
def validate_security_config(config: Dict[str, Any]) -> bool:
|
266
|
+
"""
|
267
|
+
Validate security configuration format.
|
268
|
+
|
269
|
+
Args:
|
270
|
+
config: Configuration dictionary to validate
|
271
|
+
|
272
|
+
Returns:
|
273
|
+
True if configuration is valid, False otherwise
|
274
|
+
"""
|
275
|
+
try:
|
276
|
+
# Check if security section exists
|
277
|
+
if "security" not in config:
|
278
|
+
logger.error("Security section not found in configuration")
|
279
|
+
return False
|
280
|
+
|
281
|
+
security_section = config["security"]
|
282
|
+
|
283
|
+
# Validate required fields
|
284
|
+
required_sections = ["auth", "ssl", "permissions", "rate_limit"]
|
285
|
+
for section in required_sections:
|
286
|
+
if section not in security_section:
|
287
|
+
logger.error(f"Required section '{section}' not found in security configuration")
|
288
|
+
return False
|
289
|
+
|
290
|
+
if not isinstance(security_section[section], dict):
|
291
|
+
logger.error(f"Section '{section}' must be a dictionary")
|
292
|
+
return False
|
293
|
+
|
294
|
+
# Validate auth configuration
|
295
|
+
auth_config = security_section["auth"]
|
296
|
+
if not isinstance(auth_config.get("methods", []), list):
|
297
|
+
logger.error("Auth methods must be a list")
|
298
|
+
return False
|
299
|
+
|
300
|
+
if not isinstance(auth_config.get("api_keys", {}), dict):
|
301
|
+
logger.error("API keys must be a dictionary")
|
302
|
+
return False
|
303
|
+
|
304
|
+
# Validate SSL configuration
|
305
|
+
ssl_config = security_section["ssl"]
|
306
|
+
if not isinstance(ssl_config.get("enabled", False), bool):
|
307
|
+
logger.error("SSL enabled must be a boolean")
|
308
|
+
return False
|
309
|
+
|
310
|
+
# Validate permissions configuration
|
311
|
+
permissions_config = security_section["permissions"]
|
312
|
+
if not isinstance(permissions_config.get("enabled", True), bool):
|
313
|
+
logger.error("Permissions enabled must be a boolean")
|
314
|
+
return False
|
315
|
+
|
316
|
+
# Validate rate limit configuration
|
317
|
+
rate_limit_config = security_section["rate_limit"]
|
318
|
+
if not isinstance(rate_limit_config.get("enabled", True), bool):
|
319
|
+
logger.error("Rate limit enabled must be a boolean")
|
320
|
+
return False
|
321
|
+
|
322
|
+
if not isinstance(rate_limit_config.get("requests_per_minute", 60), int):
|
323
|
+
logger.error("Requests per minute must be an integer")
|
324
|
+
return False
|
325
|
+
|
326
|
+
logger.info("Security configuration validation passed")
|
327
|
+
return True
|
328
|
+
|
329
|
+
except Exception as e:
|
330
|
+
logger.error(f"Configuration validation failed: {e}")
|
331
|
+
return False
|
332
|
+
|
333
|
+
@staticmethod
|
334
|
+
def _get_default_security_config() -> Dict[str, Any]:
|
335
|
+
"""
|
336
|
+
Get default security framework configuration.
|
337
|
+
|
338
|
+
Returns:
|
339
|
+
Default security framework configuration
|
340
|
+
"""
|
341
|
+
return {
|
342
|
+
"auth": {
|
343
|
+
"enabled": True,
|
344
|
+
"methods": ["api_key"],
|
345
|
+
"api_keys": {},
|
346
|
+
"jwt_secret": "",
|
347
|
+
"jwt_algorithm": "HS256"
|
348
|
+
},
|
349
|
+
"ssl": {
|
350
|
+
"enabled": False,
|
351
|
+
"cert_file": None,
|
352
|
+
"key_file": None,
|
353
|
+
"ca_cert": None,
|
354
|
+
"min_tls_version": "TLSv1.2",
|
355
|
+
"verify_client": False,
|
356
|
+
"client_cert_required": False
|
357
|
+
},
|
358
|
+
"permissions": {
|
359
|
+
"enabled": True,
|
360
|
+
"roles_file": "roles.json",
|
361
|
+
"default_role": "user",
|
362
|
+
"deny_by_default": True
|
363
|
+
},
|
364
|
+
"rate_limit": {
|
365
|
+
"enabled": True,
|
366
|
+
"requests_per_minute": 60,
|
367
|
+
"requests_per_hour": 1000,
|
368
|
+
"burst_limit": 10,
|
369
|
+
"by_ip": True,
|
370
|
+
"by_user": True
|
371
|
+
}
|
372
|
+
}
|
373
|
+
|
374
|
+
@staticmethod
|
375
|
+
def _get_default_mcp_config() -> Dict[str, Any]:
|
376
|
+
"""
|
377
|
+
Get default mcp_proxy_adapter configuration.
|
378
|
+
|
379
|
+
Returns:
|
380
|
+
Default mcp_proxy_adapter configuration
|
381
|
+
"""
|
382
|
+
return {
|
383
|
+
"security": {
|
384
|
+
"framework": "mcp_security_framework",
|
385
|
+
"enabled": True,
|
386
|
+
"auth": {
|
387
|
+
"enabled": True,
|
388
|
+
"methods": ["api_key"],
|
389
|
+
"api_keys": {}
|
390
|
+
},
|
391
|
+
"ssl": {
|
392
|
+
"enabled": False,
|
393
|
+
"cert_file": None,
|
394
|
+
"key_file": None
|
395
|
+
},
|
396
|
+
"permissions": {
|
397
|
+
"enabled": True,
|
398
|
+
"roles_file": "roles.json"
|
399
|
+
},
|
400
|
+
"rate_limit": {
|
401
|
+
"enabled": True,
|
402
|
+
"requests_per_minute": 60
|
403
|
+
}
|
404
|
+
}
|
405
|
+
}
|
@@ -0,0 +1,218 @@
|
|
1
|
+
"""
|
2
|
+
Configuration validator for strict validation before startup.
|
3
|
+
|
4
|
+
This module provides strict validation of configuration to prevent startup
|
5
|
+
with invalid or insecure configurations.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import os
|
9
|
+
import json
|
10
|
+
from typing import Dict, Any, List, Optional
|
11
|
+
from pathlib import Path
|
12
|
+
|
13
|
+
from mcp_proxy_adapter.core.logging import logger
|
14
|
+
|
15
|
+
|
16
|
+
class ConfigValidator:
|
17
|
+
"""
|
18
|
+
Strict configuration validator.
|
19
|
+
|
20
|
+
Validates configuration before startup and prevents startup
|
21
|
+
with invalid or insecure configurations.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(self, config: Dict[str, Any]):
|
25
|
+
"""
|
26
|
+
Initialize configuration validator.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
config: Configuration dictionary to validate
|
30
|
+
"""
|
31
|
+
self.config = config
|
32
|
+
self.errors: List[str] = []
|
33
|
+
self.warnings: List[str] = []
|
34
|
+
|
35
|
+
def validate_all(self) -> bool:
|
36
|
+
"""
|
37
|
+
Validate all configuration sections.
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
True if configuration is valid, False otherwise
|
41
|
+
"""
|
42
|
+
self.errors.clear()
|
43
|
+
self.warnings.clear()
|
44
|
+
|
45
|
+
# Validate basic structure
|
46
|
+
if not self._validate_basic_structure():
|
47
|
+
return False
|
48
|
+
|
49
|
+
# Validate server configuration
|
50
|
+
if not self._validate_server_config():
|
51
|
+
return False
|
52
|
+
|
53
|
+
# Validate security configuration
|
54
|
+
if not self._validate_security_config():
|
55
|
+
return False
|
56
|
+
|
57
|
+
# Validate commands configuration
|
58
|
+
if not self._validate_commands_config():
|
59
|
+
return False
|
60
|
+
|
61
|
+
# Validate SSL configuration
|
62
|
+
if not self._validate_ssl_config():
|
63
|
+
return False
|
64
|
+
|
65
|
+
# Validate roles configuration
|
66
|
+
if not self._validate_roles_config():
|
67
|
+
return False
|
68
|
+
|
69
|
+
# Log warnings if any
|
70
|
+
if self.warnings:
|
71
|
+
for warning in self.warnings:
|
72
|
+
logger.warning(f"Configuration warning: {warning}")
|
73
|
+
|
74
|
+
# Return success only if no errors
|
75
|
+
return len(self.errors) == 0
|
76
|
+
|
77
|
+
def _validate_basic_structure(self) -> bool:
|
78
|
+
"""Validate basic configuration structure."""
|
79
|
+
required_sections = ["server", "logging", "commands"]
|
80
|
+
|
81
|
+
for section in required_sections:
|
82
|
+
if section not in self.config:
|
83
|
+
self.errors.append(f"Missing required configuration section: {section}")
|
84
|
+
|
85
|
+
return len(self.errors) == 0
|
86
|
+
|
87
|
+
def _validate_server_config(self) -> bool:
|
88
|
+
"""Validate server configuration."""
|
89
|
+
server_config = self.config.get("server", {})
|
90
|
+
|
91
|
+
# Validate host
|
92
|
+
host = server_config.get("host")
|
93
|
+
if not host:
|
94
|
+
self.errors.append("Server host is required")
|
95
|
+
|
96
|
+
# Validate port
|
97
|
+
port = server_config.get("port")
|
98
|
+
if not isinstance(port, int) or port < 1 or port > 65535:
|
99
|
+
self.errors.append("Server port must be an integer between 1 and 65535")
|
100
|
+
|
101
|
+
return len(self.errors) == 0
|
102
|
+
|
103
|
+
def _validate_security_config(self) -> bool:
|
104
|
+
"""Validate security configuration."""
|
105
|
+
security_config = self.config.get("security", {})
|
106
|
+
|
107
|
+
# Check if security is enabled
|
108
|
+
security_enabled = security_config.get("enabled", True)
|
109
|
+
auth_enabled = self.config.get("auth_enabled", False)
|
110
|
+
|
111
|
+
if security_enabled and auth_enabled:
|
112
|
+
# Validate auth configuration
|
113
|
+
auth_config = security_config.get("auth", {})
|
114
|
+
if not auth_config.get("enabled", False):
|
115
|
+
self.errors.append("Security is enabled but auth is disabled")
|
116
|
+
return False
|
117
|
+
|
118
|
+
# Validate API keys if auth is enabled
|
119
|
+
if auth_config.get("enabled", False):
|
120
|
+
api_keys = auth_config.get("api_keys", {})
|
121
|
+
if not api_keys:
|
122
|
+
self.errors.append("API keys are required when authentication is enabled")
|
123
|
+
return False
|
124
|
+
|
125
|
+
# Validate API key format
|
126
|
+
for key, value in api_keys.items():
|
127
|
+
if not key or not value:
|
128
|
+
self.errors.append("API keys must have non-empty key and value")
|
129
|
+
return False
|
130
|
+
|
131
|
+
return len(self.errors) == 0
|
132
|
+
|
133
|
+
def _validate_commands_config(self) -> bool:
|
134
|
+
"""Validate commands configuration."""
|
135
|
+
commands_config = self.config.get("commands", {})
|
136
|
+
|
137
|
+
# Validate commands directory if auto_discovery is enabled
|
138
|
+
if commands_config.get("auto_discovery", True):
|
139
|
+
commands_dir = commands_config.get("commands_directory", "./commands")
|
140
|
+
if not os.path.exists(commands_dir):
|
141
|
+
self.warnings.append(f"Commands directory does not exist: {commands_dir}")
|
142
|
+
|
143
|
+
return True
|
144
|
+
|
145
|
+
def _validate_ssl_config(self) -> bool:
|
146
|
+
"""Validate SSL configuration."""
|
147
|
+
ssl_config = self.config.get("ssl", {})
|
148
|
+
ssl_enabled = ssl_config.get("enabled", False)
|
149
|
+
|
150
|
+
if ssl_enabled:
|
151
|
+
# Validate certificate files
|
152
|
+
cert_file = ssl_config.get("cert_file")
|
153
|
+
key_file = ssl_config.get("key_file")
|
154
|
+
|
155
|
+
if not cert_file or not key_file:
|
156
|
+
self.errors.append("SSL certificate and key files are required when SSL is enabled")
|
157
|
+
return False
|
158
|
+
|
159
|
+
if not os.path.exists(cert_file):
|
160
|
+
self.errors.append(f"SSL certificate file not found: {cert_file}")
|
161
|
+
|
162
|
+
if not os.path.exists(key_file):
|
163
|
+
self.errors.append(f"SSL private key file not found: {key_file}")
|
164
|
+
|
165
|
+
return len(self.errors) == 0
|
166
|
+
|
167
|
+
def _validate_roles_config(self) -> bool:
|
168
|
+
"""Validate roles configuration."""
|
169
|
+
roles_config = self.config.get("roles", {})
|
170
|
+
roles_enabled = roles_config.get("enabled", False)
|
171
|
+
|
172
|
+
if roles_enabled:
|
173
|
+
config_file = roles_config.get("config_file")
|
174
|
+
if not config_file:
|
175
|
+
self.errors.append("Roles config file is required when roles are enabled")
|
176
|
+
return False
|
177
|
+
|
178
|
+
if not os.path.exists(config_file):
|
179
|
+
self.errors.append(f"Roles config file not found: {config_file}")
|
180
|
+
return False
|
181
|
+
|
182
|
+
# Validate roles schema file
|
183
|
+
try:
|
184
|
+
with open(config_file, 'r') as f:
|
185
|
+
roles_schema = json.load(f)
|
186
|
+
|
187
|
+
if "roles" not in roles_schema:
|
188
|
+
self.errors.append("Roles config file must contain 'roles' section")
|
189
|
+
return False
|
190
|
+
|
191
|
+
except (json.JSONDecodeError, IOError) as e:
|
192
|
+
self.errors.append(f"Failed to read roles config file: {e}")
|
193
|
+
return False
|
194
|
+
|
195
|
+
return len(self.errors) == 0
|
196
|
+
|
197
|
+
def get_errors(self) -> List[str]:
|
198
|
+
"""Get validation errors."""
|
199
|
+
return self.errors.copy()
|
200
|
+
|
201
|
+
def get_warnings(self) -> List[str]:
|
202
|
+
"""Get validation warnings."""
|
203
|
+
return self.warnings.copy()
|
204
|
+
|
205
|
+
def print_validation_report(self):
|
206
|
+
"""Print validation report."""
|
207
|
+
if self.errors:
|
208
|
+
logger.error("Configuration validation failed:")
|
209
|
+
for error in self.errors:
|
210
|
+
logger.error(f" - {error}")
|
211
|
+
|
212
|
+
if self.warnings:
|
213
|
+
logger.warning("Configuration warnings:")
|
214
|
+
for warning in self.warnings:
|
215
|
+
logger.warning(f" - {warning}")
|
216
|
+
|
217
|
+
if not self.errors and not self.warnings:
|
218
|
+
logger.info("Configuration validation passed")
|
@@ -121,6 +121,17 @@ def setup_logging(
|
|
121
121
|
"""
|
122
122
|
# Get parameters from configuration if not explicitly specified
|
123
123
|
level = level or config.get("logging.level", "INFO")
|
124
|
+
|
125
|
+
# Check debug level from config if debug is enabled
|
126
|
+
if config.get("debug.enabled", False):
|
127
|
+
debug_level = config.get("debug.level", "INFO")
|
128
|
+
# Use debug level if it's more verbose than the logging level
|
129
|
+
debug_level_num = getattr(logging, debug_level.upper(), logging.INFO)
|
130
|
+
level_num = getattr(logging, level.upper(), logging.INFO)
|
131
|
+
if debug_level_num < level_num:
|
132
|
+
level = debug_level
|
133
|
+
logger.debug(f"Using debug level from config: {debug_level}")
|
134
|
+
|
124
135
|
log_file = log_file or config.get("logging.file")
|
125
136
|
rotation_type = rotation_type or "size" # Default to size-based rotation
|
126
137
|
|