mcp-proxy-adapter 6.0.0__py3-none-any.whl ā 6.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_proxy_adapter/api/app.py +174 -80
- mcp_proxy_adapter/api/handlers.py +16 -5
- mcp_proxy_adapter/api/middleware/__init__.py +9 -4
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
- mcp_proxy_adapter/api/middleware/factory.py +36 -12
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +32 -13
- mcp_proxy_adapter/api/middleware/unified_security.py +160 -0
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
- mcp_proxy_adapter/commands/__init__.py +7 -1
- mcp_proxy_adapter/commands/base.py +7 -4
- mcp_proxy_adapter/commands/builtin_commands.py +8 -2
- mcp_proxy_adapter/commands/command_registry.py +8 -0
- mcp_proxy_adapter/commands/echo_command.py +81 -0
- mcp_proxy_adapter/commands/help_command.py +21 -14
- mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
- mcp_proxy_adapter/commands/role_test_command.py +141 -0
- mcp_proxy_adapter/commands/security_command.py +488 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +2 -2
- mcp_proxy_adapter/commands/token_management_command.py +1 -1
- mcp_proxy_adapter/config.py +81 -21
- mcp_proxy_adapter/core/app_factory.py +326 -0
- mcp_proxy_adapter/core/client_security.py +384 -0
- mcp_proxy_adapter/core/logging.py +8 -3
- mcp_proxy_adapter/core/mtls_asgi.py +156 -0
- mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
- mcp_proxy_adapter/core/protocol_manager.py +139 -8
- mcp_proxy_adapter/core/proxy_client.py +602 -0
- mcp_proxy_adapter/core/proxy_registration.py +299 -47
- mcp_proxy_adapter/core/security_adapter.py +12 -15
- mcp_proxy_adapter/core/security_integration.py +285 -0
- mcp_proxy_adapter/core/server_adapter.py +345 -0
- mcp_proxy_adapter/core/server_engine.py +364 -0
- mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
- mcp_proxy_adapter/docs/EN/TROUBLESHOOTING.md +285 -0
- mcp_proxy_adapter/docs/RU/TROUBLESHOOTING.md +285 -0
- mcp_proxy_adapter/examples/README.md +230 -97
- mcp_proxy_adapter/examples/README_EN.md +258 -0
- mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
- mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
- mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
- mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +43 -0
- mcp_proxy_adapter/examples/basic_framework/configs/https_no_protocol_middleware.json +36 -0
- mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +29 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_protocol_middleware.json +34 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_simple.json +35 -0
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
- mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
- mcp_proxy_adapter/examples/cert_config.json +9 -0
- mcp_proxy_adapter/examples/certs/admin.crt +32 -0
- mcp_proxy_adapter/examples/certs/admin.key +52 -0
- mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
- mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
- mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
- mcp_proxy_adapter/examples/certs/client.crt +32 -0
- mcp_proxy_adapter/examples/certs/client.key +52 -0
- mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
- mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
- mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
- mcp_proxy_adapter/examples/certs/client_user.key +52 -0
- mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
- mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
- mcp_proxy_adapter/examples/certs/readonly.key +52 -0
- mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
- mcp_proxy_adapter/examples/certs/server.crt +32 -0
- mcp_proxy_adapter/examples/certs/server.key +52 -0
- mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
- mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
- mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
- mcp_proxy_adapter/examples/certs/user.crt +32 -0
- mcp_proxy_adapter/examples/certs/user.key +52 -0
- mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
- mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
- mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
- mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
- mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
- mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
- mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
- mcp_proxy_adapter/examples/commands/__init__.py +1 -0
- mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
- mcp_proxy_adapter/examples/debug_request_state.py +144 -0
- mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
- mcp_proxy_adapter/examples/demo_client.py +341 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
- mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
- mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
- mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
- mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
- mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
- mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
- mcp_proxy_adapter/examples/full_application/main.py +138 -0
- mcp_proxy_adapter/examples/full_application/roles.json +21 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
- mcp_proxy_adapter/examples/generate_certificates.py +121 -0
- mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
- mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
- mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
- mcp_proxy_adapter/examples/roles.json +38 -0
- mcp_proxy_adapter/examples/run_example.py +81 -0
- mcp_proxy_adapter/examples/run_security_tests.py +326 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
- mcp_proxy_adapter/examples/security_test_client.py +743 -0
- mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
- mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
- mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
- mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
- mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
- mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
- mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
- mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
- mcp_proxy_adapter/examples/test_config_generator.py +110 -0
- mcp_proxy_adapter/examples/test_examples.py +344 -0
- mcp_proxy_adapter/examples/universal_client.py +628 -0
- mcp_proxy_adapter/main.py +21 -10
- mcp_proxy_adapter/utils/config_generator.py +727 -0
- mcp_proxy_adapter/version.py +5 -2
- mcp_proxy_adapter-6.1.1.dist-info/METADATA +205 -0
- mcp_proxy_adapter-6.1.1.dist-info/RECORD +197 -0
- mcp_proxy_adapter-6.1.1.dist-info/entry_points.txt +2 -0
- {mcp_proxy_adapter-6.0.0.dist-info ā mcp_proxy_adapter-6.1.1.dist-info}/licenses/LICENSE +2 -2
- mcp_proxy_adapter/api/middleware/auth.py +0 -146
- mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
- mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
- mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
- mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
- mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
- mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
- mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
- mcp_proxy_adapter/api/middleware/security.py +0 -376
- mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
- mcp_proxy_adapter/examples/__init__.py +0 -7
- mcp_proxy_adapter/examples/basic_server/README.md +0 -60
- mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
- mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
- mcp_proxy_adapter/examples/basic_server/config.json +0 -70
- mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
- mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
- mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
- mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
- mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
- mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
- mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
- mcp_proxy_adapter/examples/basic_server/server.py +0 -114
- mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
- mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
- mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
- mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
- mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
- mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
- mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
- mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
- mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
- mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
- mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
- mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
- mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
- mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
- mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
- mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
- mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
- mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
- mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
- mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
- mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
- mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
- mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
- mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
- mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
- mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
- mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
- mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
- mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
- mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
- mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
- mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
- mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
- mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
- mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
- mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
- mcp_proxy_adapter/examples/deployment/README.md +0 -49
- mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
- mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
- mcp_proxy_adapter/examples/deployment/config.json +0 -29
- mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
- mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
- mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
- mcp_proxy_adapter/examples/deployment/run.sh +0 -43
- mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
- mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
- mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
- mcp_proxy_adapter/schemas/base_schema.json +0 -114
- mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
- mcp_proxy_adapter/schemas/roles_schema.json +0 -162
- mcp_proxy_adapter/tests/__init__.py +0 -0
- mcp_proxy_adapter/tests/api/__init__.py +0 -3
- mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
- mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
- mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
- mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
- mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
- mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
- mcp_proxy_adapter/tests/commands/__init__.py +0 -3
- mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
- mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
- mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
- mcp_proxy_adapter/tests/conftest.py +0 -131
- mcp_proxy_adapter/tests/functional/__init__.py +0 -3
- mcp_proxy_adapter/tests/functional/test_api.py +0 -253
- mcp_proxy_adapter/tests/integration/__init__.py +0 -3
- mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
- mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
- mcp_proxy_adapter/tests/performance/__init__.py +0 -3
- mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
- mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
- mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
- mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
- mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
- mcp_proxy_adapter/tests/test_base_command.py +0 -123
- mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
- mcp_proxy_adapter/tests/test_command_registry.py +0 -281
- mcp_proxy_adapter/tests/test_config.py +0 -127
- mcp_proxy_adapter/tests/test_utils.py +0 -65
- mcp_proxy_adapter/tests/unit/__init__.py +0 -3
- mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
- mcp_proxy_adapter/tests/unit/test_config.py +0 -270
- mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
- mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
- {mcp_proxy_adapter-6.0.0.dist-info ā mcp_proxy_adapter-6.1.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.0.0.dist-info ā mcp_proxy_adapter-6.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,326 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Security Test Runner for MCP Proxy Adapter
|
4
|
+
|
5
|
+
This script runs comprehensive security tests against all server configurations:
|
6
|
+
- Basic HTTP
|
7
|
+
- HTTP + Token authentication
|
8
|
+
- HTTPS
|
9
|
+
- HTTPS + Token authentication
|
10
|
+
- mTLS
|
11
|
+
|
12
|
+
Author: Vasiliy Zdanovskiy
|
13
|
+
email: vasilyvz@gmail.com
|
14
|
+
"""
|
15
|
+
|
16
|
+
import asyncio
|
17
|
+
import json
|
18
|
+
import os
|
19
|
+
import signal
|
20
|
+
import subprocess
|
21
|
+
import sys
|
22
|
+
import time
|
23
|
+
from pathlib import Path
|
24
|
+
from typing import Dict, List, Optional, Any
|
25
|
+
|
26
|
+
# Add project root to path
|
27
|
+
project_root = Path(__file__).parent.parent.parent
|
28
|
+
sys.path.insert(0, str(project_root))
|
29
|
+
|
30
|
+
from security_test_client import SecurityTestClient, TestResult
|
31
|
+
|
32
|
+
|
33
|
+
class SecurityTestRunner:
|
34
|
+
"""Main test runner for security testing."""
|
35
|
+
|
36
|
+
def __init__(self):
|
37
|
+
"""Initialize test runner."""
|
38
|
+
self.servers = {}
|
39
|
+
self.test_results = {}
|
40
|
+
self.configs = {
|
41
|
+
"basic_http": {
|
42
|
+
"config": "server_configs/config_basic_http.json",
|
43
|
+
"port": 8000,
|
44
|
+
"url": "http://localhost:8000",
|
45
|
+
"auth": "none"
|
46
|
+
},
|
47
|
+
"http_token": {
|
48
|
+
"config": "server_configs/config_http_token.json",
|
49
|
+
"port": 8001,
|
50
|
+
"url": "http://localhost:8001",
|
51
|
+
"auth": "api_key"
|
52
|
+
},
|
53
|
+
"https": {
|
54
|
+
"config": "server_configs/config_https.json",
|
55
|
+
"port": 8443,
|
56
|
+
"url": "https://localhost:8443",
|
57
|
+
"auth": "none"
|
58
|
+
},
|
59
|
+
"https_token": {
|
60
|
+
"config": "server_configs/config_https_token.json",
|
61
|
+
"port": 8444,
|
62
|
+
"url": "https://localhost:8444",
|
63
|
+
"auth": "api_key"
|
64
|
+
},
|
65
|
+
"mtls": {
|
66
|
+
"config": "server_configs/config_mtls.json",
|
67
|
+
"port": 8445,
|
68
|
+
"url": "https://localhost:8445",
|
69
|
+
"auth": "certificate"
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
def check_prerequisites(self) -> bool:
|
74
|
+
"""Check if all prerequisites are met."""
|
75
|
+
print("š Checking prerequisites...")
|
76
|
+
|
77
|
+
# Check if we're in the right directory
|
78
|
+
if not Path("server_configs").exists():
|
79
|
+
print("ā server_configs directory not found. Please run from mcp_proxy_adapter/examples/")
|
80
|
+
return False
|
81
|
+
|
82
|
+
# Check if certificates exist
|
83
|
+
cert_files = [
|
84
|
+
"certs/ca_cert.pem",
|
85
|
+
"certs/server_cert.pem",
|
86
|
+
"certs/server_key.pem"
|
87
|
+
]
|
88
|
+
|
89
|
+
missing_certs = []
|
90
|
+
for cert_file in cert_files:
|
91
|
+
if not Path(cert_file).exists():
|
92
|
+
missing_certs.append(cert_file)
|
93
|
+
|
94
|
+
if missing_certs:
|
95
|
+
print(f"ā Missing certificates: {missing_certs}")
|
96
|
+
print("š” Run: python generate_certificates.py")
|
97
|
+
return False
|
98
|
+
|
99
|
+
print("ā
Prerequisites check passed")
|
100
|
+
return True
|
101
|
+
|
102
|
+
def start_server(self, name: str, config_path: str, port: int) -> Optional[subprocess.Popen]:
|
103
|
+
"""Start a server in background."""
|
104
|
+
try:
|
105
|
+
print(f"š Starting {name} server on port {port}...")
|
106
|
+
|
107
|
+
# Start server in background
|
108
|
+
process = subprocess.Popen([
|
109
|
+
sys.executable, "-m", "mcp_proxy_adapter.main",
|
110
|
+
"--config", config_path
|
111
|
+
], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
112
|
+
|
113
|
+
# Wait a bit for server to start
|
114
|
+
time.sleep(3)
|
115
|
+
|
116
|
+
# Check if process is still running
|
117
|
+
if process.poll() is None:
|
118
|
+
print(f"ā
{name} server started (PID: {process.pid})")
|
119
|
+
return process
|
120
|
+
else:
|
121
|
+
stdout, stderr = process.communicate()
|
122
|
+
print(f"ā Failed to start {name} server:")
|
123
|
+
print(f"STDOUT: {stdout.decode()}")
|
124
|
+
print(f"STDERR: {stderr.decode()}")
|
125
|
+
return None
|
126
|
+
|
127
|
+
except Exception as e:
|
128
|
+
print(f"ā Error starting {name} server: {e}")
|
129
|
+
return None
|
130
|
+
|
131
|
+
def stop_server(self, name: str, process: subprocess.Popen):
|
132
|
+
"""Stop a server."""
|
133
|
+
try:
|
134
|
+
print(f"š Stopping {name} server (PID: {process.pid})...")
|
135
|
+
process.terminate()
|
136
|
+
|
137
|
+
# Wait for graceful shutdown
|
138
|
+
try:
|
139
|
+
process.wait(timeout=5)
|
140
|
+
print(f"ā
{name} server stopped")
|
141
|
+
except subprocess.TimeoutExpired:
|
142
|
+
print(f"ā ļø Force killing {name} server")
|
143
|
+
process.kill()
|
144
|
+
process.wait()
|
145
|
+
|
146
|
+
except Exception as e:
|
147
|
+
print(f"ā Error stopping {name} server: {e}")
|
148
|
+
|
149
|
+
async def test_server(self, name: str, config: Dict[str, Any]) -> List[TestResult]:
|
150
|
+
"""Test a specific server configuration."""
|
151
|
+
print(f"\nš§Ŗ Testing {name} server...")
|
152
|
+
print("=" * 50)
|
153
|
+
|
154
|
+
# Create client with appropriate SSL context
|
155
|
+
if config["auth"] == "certificate":
|
156
|
+
# For mTLS, create client with certificate-based SSL context
|
157
|
+
client = SecurityTestClient(config["url"])
|
158
|
+
# Override SSL context for mTLS
|
159
|
+
client.create_ssl_context = client.create_ssl_context_for_mtls
|
160
|
+
async with client as client_session:
|
161
|
+
results = await client_session.run_security_tests(
|
162
|
+
config["url"],
|
163
|
+
config["auth"]
|
164
|
+
)
|
165
|
+
else:
|
166
|
+
# For other auth types, use default SSL context
|
167
|
+
async with SecurityTestClient(config["url"]) as client:
|
168
|
+
results = await client.run_security_tests(
|
169
|
+
config["url"],
|
170
|
+
config["auth"]
|
171
|
+
)
|
172
|
+
|
173
|
+
# Print summary for this server
|
174
|
+
passed = sum(1 for r in results if r.success)
|
175
|
+
total = len(results)
|
176
|
+
print(f"\nš {name} Results: {passed}/{total} tests passed")
|
177
|
+
|
178
|
+
return results
|
179
|
+
|
180
|
+
async def run_all_tests(self) -> Dict[str, List[TestResult]]:
|
181
|
+
"""Run tests against all server configurations."""
|
182
|
+
print("š Starting comprehensive security testing")
|
183
|
+
print("=" * 60)
|
184
|
+
|
185
|
+
# Start all servers
|
186
|
+
for name, config in self.configs.items():
|
187
|
+
process = self.start_server(name, config["config"], config["port"])
|
188
|
+
if process:
|
189
|
+
self.servers[name] = process
|
190
|
+
else:
|
191
|
+
print(f"ā ļø Skipping tests for {name} due to startup failure")
|
192
|
+
|
193
|
+
# Wait for all servers to be ready
|
194
|
+
print("\nā³ Waiting for servers to be ready...")
|
195
|
+
time.sleep(5)
|
196
|
+
|
197
|
+
# Test each server
|
198
|
+
all_results = {}
|
199
|
+
for name, config in self.configs.items():
|
200
|
+
if name in self.servers:
|
201
|
+
try:
|
202
|
+
results = await self.test_server(name, config)
|
203
|
+
all_results[name] = results
|
204
|
+
except Exception as e:
|
205
|
+
print(f"ā Error testing {name}: {e}")
|
206
|
+
all_results[name] = []
|
207
|
+
else:
|
208
|
+
print(f"ā ļø Skipping {name} tests (server not running)")
|
209
|
+
all_results[name] = []
|
210
|
+
|
211
|
+
return all_results
|
212
|
+
|
213
|
+
def print_final_summary(self, all_results: Dict[str, List[TestResult]]):
|
214
|
+
"""Print final test summary."""
|
215
|
+
print("\n" + "=" * 80)
|
216
|
+
print("š FINAL SECURITY TEST SUMMARY")
|
217
|
+
print("=" * 80)
|
218
|
+
|
219
|
+
total_tests = 0
|
220
|
+
total_passed = 0
|
221
|
+
|
222
|
+
for server_name, results in all_results.items():
|
223
|
+
if results:
|
224
|
+
passed = sum(1 for r in results if r.success)
|
225
|
+
total = len(results)
|
226
|
+
total_tests += total
|
227
|
+
total_passed += passed
|
228
|
+
|
229
|
+
status = "ā
PASS" if passed == total else "ā FAIL"
|
230
|
+
print(f"{status} {server_name.upper()}: {passed}/{total} tests passed")
|
231
|
+
|
232
|
+
# Show failed tests
|
233
|
+
failed_tests = [r for r in results if not r.success]
|
234
|
+
for test in failed_tests:
|
235
|
+
print(f" ā {test.test_name}: {test.error_message}")
|
236
|
+
else:
|
237
|
+
print(f"ā ļø SKIP {server_name.upper()}: No tests run")
|
238
|
+
|
239
|
+
print("\n" + "-" * 80)
|
240
|
+
print(f"OVERALL: {total_passed}/{total_tests} tests passed")
|
241
|
+
if total_tests > 0:
|
242
|
+
success_rate = (total_passed / total_tests) * 100
|
243
|
+
print(f"SUCCESS RATE: {success_rate:.1f}%")
|
244
|
+
|
245
|
+
# Overall status
|
246
|
+
if total_passed == total_tests and total_tests > 0:
|
247
|
+
print("š ALL TESTS PASSED!")
|
248
|
+
elif total_passed > 0:
|
249
|
+
print("ā ļø SOME TESTS FAILED")
|
250
|
+
else:
|
251
|
+
print("ā ALL TESTS FAILED")
|
252
|
+
|
253
|
+
def cleanup(self):
|
254
|
+
"""Cleanup all running servers."""
|
255
|
+
print("\nš§¹ Cleaning up...")
|
256
|
+
|
257
|
+
for name, process in self.servers.items():
|
258
|
+
self.stop_server(name, process)
|
259
|
+
|
260
|
+
self.servers.clear()
|
261
|
+
|
262
|
+
def signal_handler(self, signum, frame):
|
263
|
+
"""Handle interrupt signals."""
|
264
|
+
print(f"\nā ļø Received signal {signum}, cleaning up...")
|
265
|
+
self.cleanup()
|
266
|
+
sys.exit(0)
|
267
|
+
|
268
|
+
async def run(self):
|
269
|
+
"""Main run method."""
|
270
|
+
# Set up signal handlers
|
271
|
+
signal.signal(signal.SIGINT, self.signal_handler)
|
272
|
+
signal.signal(signal.SIGTERM, self.signal_handler)
|
273
|
+
|
274
|
+
try:
|
275
|
+
# Check prerequisites
|
276
|
+
if not self.check_prerequisites():
|
277
|
+
return False
|
278
|
+
|
279
|
+
# Run all tests
|
280
|
+
all_results = await self.run_all_tests()
|
281
|
+
|
282
|
+
# Print summary
|
283
|
+
self.print_final_summary(all_results)
|
284
|
+
|
285
|
+
return True
|
286
|
+
|
287
|
+
except Exception as e:
|
288
|
+
print(f"ā Test runner error: {e}")
|
289
|
+
return False
|
290
|
+
|
291
|
+
finally:
|
292
|
+
# Always cleanup
|
293
|
+
self.cleanup()
|
294
|
+
|
295
|
+
|
296
|
+
def main():
|
297
|
+
"""Main function."""
|
298
|
+
import argparse
|
299
|
+
|
300
|
+
parser = argparse.ArgumentParser(description="Security Test Runner for MCP Proxy Adapter")
|
301
|
+
parser.add_argument("--config", help="Test specific configuration")
|
302
|
+
parser.add_argument("--no-cleanup", action="store_true", help="Don't cleanup servers after tests")
|
303
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
304
|
+
|
305
|
+
args = parser.parse_args()
|
306
|
+
|
307
|
+
# Change to examples directory
|
308
|
+
examples_dir = Path(__file__).parent
|
309
|
+
os.chdir(examples_dir)
|
310
|
+
|
311
|
+
# Create and run test runner
|
312
|
+
runner = SecurityTestRunner()
|
313
|
+
|
314
|
+
try:
|
315
|
+
success = asyncio.run(runner.run())
|
316
|
+
sys.exit(0 if success else 1)
|
317
|
+
except KeyboardInterrupt:
|
318
|
+
print("\nā ļø Interrupted by user")
|
319
|
+
sys.exit(1)
|
320
|
+
except Exception as e:
|
321
|
+
print(f"ā Unexpected error: {e}")
|
322
|
+
sys.exit(1)
|
323
|
+
|
324
|
+
|
325
|
+
if __name__ == "__main__":
|
326
|
+
main()
|
@@ -0,0 +1,300 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Security Testing Script - Fixed Version
|
4
|
+
|
5
|
+
This script runs comprehensive security tests without fallback mode
|
6
|
+
and with proper port management.
|
7
|
+
|
8
|
+
Author: Vasiliy Zdanovskiy
|
9
|
+
email: vasilyvz@gmail.com
|
10
|
+
"""
|
11
|
+
|
12
|
+
import asyncio
|
13
|
+
import json
|
14
|
+
import os
|
15
|
+
import signal
|
16
|
+
import subprocess
|
17
|
+
import sys
|
18
|
+
import time
|
19
|
+
from pathlib import Path
|
20
|
+
from typing import Dict, List, Optional, Tuple
|
21
|
+
|
22
|
+
# Add project root to path
|
23
|
+
project_root = Path(__file__).parent.parent.parent
|
24
|
+
sys.path.insert(0, str(project_root))
|
25
|
+
|
26
|
+
from security_test_client import SecurityTestClient, TestResult
|
27
|
+
|
28
|
+
|
29
|
+
class SecurityTestRunner:
|
30
|
+
"""Security test runner with proper port management."""
|
31
|
+
|
32
|
+
def __init__(self):
|
33
|
+
self.project_root = Path(__file__).parent.parent.parent
|
34
|
+
self.configs_dir = self.project_root / "mcp_proxy_adapter" / "examples" / "server_configs"
|
35
|
+
self.server_processes = {}
|
36
|
+
self.test_results = []
|
37
|
+
|
38
|
+
def kill_process_on_port(self, port: int) -> bool:
|
39
|
+
"""Kill process using specific port."""
|
40
|
+
try:
|
41
|
+
# Find process using the port
|
42
|
+
result = subprocess.run(
|
43
|
+
["lsof", "-ti", f":{port}"],
|
44
|
+
capture_output=True,
|
45
|
+
text=True,
|
46
|
+
timeout=5
|
47
|
+
)
|
48
|
+
|
49
|
+
if result.returncode == 0 and result.stdout.strip():
|
50
|
+
pid = result.stdout.strip()
|
51
|
+
# Kill the process
|
52
|
+
subprocess.run(["kill", "-9", pid], check=True)
|
53
|
+
print(f"ā
Killed process {pid} on port {port}")
|
54
|
+
time.sleep(1) # Wait for port to be released
|
55
|
+
return True
|
56
|
+
else:
|
57
|
+
print(f"ā¹ļø No process found on port {port}")
|
58
|
+
return True
|
59
|
+
except subprocess.TimeoutExpired:
|
60
|
+
print(f"ā ļø Timeout checking port {port}")
|
61
|
+
return False
|
62
|
+
except Exception as e:
|
63
|
+
print(f"ā Error killing process on port {port}: {e}")
|
64
|
+
return False
|
65
|
+
|
66
|
+
def start_server(self, config_name: str, config_path: Path) -> Optional[subprocess.Popen]:
|
67
|
+
"""Start server with proper error handling."""
|
68
|
+
try:
|
69
|
+
# Get port from config
|
70
|
+
with open(config_path) as f:
|
71
|
+
config = json.load(f)
|
72
|
+
port = config.get("server", {}).get("port", 8000)
|
73
|
+
|
74
|
+
# Kill any existing process on this port
|
75
|
+
self.kill_process_on_port(port)
|
76
|
+
|
77
|
+
# Start server
|
78
|
+
cmd = [
|
79
|
+
sys.executable, "-m", "mcp_proxy_adapter.main",
|
80
|
+
"--config", str(config_path)
|
81
|
+
]
|
82
|
+
|
83
|
+
# For mTLS, start from examples directory
|
84
|
+
if config_name == "mtls":
|
85
|
+
cwd = self.project_root / "mcp_proxy_adapter" / "examples"
|
86
|
+
else:
|
87
|
+
cwd = self.project_root
|
88
|
+
|
89
|
+
print(f"š Starting {config_name} on port {port}...")
|
90
|
+
process = subprocess.Popen(
|
91
|
+
cmd,
|
92
|
+
cwd=cwd,
|
93
|
+
stdout=subprocess.PIPE,
|
94
|
+
stderr=subprocess.PIPE,
|
95
|
+
text=True
|
96
|
+
)
|
97
|
+
|
98
|
+
# Wait a bit for server to start
|
99
|
+
time.sleep(3)
|
100
|
+
|
101
|
+
# Check if process is still running
|
102
|
+
if process.poll() is None:
|
103
|
+
print(f"ā
{config_name} started successfully on port {port}")
|
104
|
+
return process
|
105
|
+
else:
|
106
|
+
stdout, stderr = process.communicate()
|
107
|
+
print(f"ā {config_name} failed to start:")
|
108
|
+
print(f"STDOUT: {stdout}")
|
109
|
+
print(f"STDERR: {stderr}")
|
110
|
+
return None
|
111
|
+
|
112
|
+
except Exception as e:
|
113
|
+
print(f"ā Error starting {config_name}: {e}")
|
114
|
+
return None
|
115
|
+
|
116
|
+
def stop_server(self, config_name: str, process: subprocess.Popen):
|
117
|
+
"""Stop server gracefully."""
|
118
|
+
try:
|
119
|
+
print(f"š Stopping {config_name}...")
|
120
|
+
process.terminate()
|
121
|
+
process.wait(timeout=5)
|
122
|
+
print(f"ā
{config_name} stopped")
|
123
|
+
except subprocess.TimeoutExpired:
|
124
|
+
print(f"ā ļø Force killing {config_name}...")
|
125
|
+
process.kill()
|
126
|
+
process.wait()
|
127
|
+
except Exception as e:
|
128
|
+
print(f"ā Error stopping {config_name}: {e}")
|
129
|
+
|
130
|
+
async def test_server(self, config_name: str, config_path: Path) -> List[TestResult]:
|
131
|
+
"""Test a single server configuration."""
|
132
|
+
results = []
|
133
|
+
|
134
|
+
# Start server
|
135
|
+
process = self.start_server(config_name, config_path)
|
136
|
+
if not process:
|
137
|
+
return [TestResult(
|
138
|
+
test_name=f"{config_name}_startup",
|
139
|
+
server_url=f"http://localhost:{port}",
|
140
|
+
auth_type="none",
|
141
|
+
success=False,
|
142
|
+
error_message="Server failed to start"
|
143
|
+
)]
|
144
|
+
|
145
|
+
try:
|
146
|
+
# Get config for client setup
|
147
|
+
with open(config_path) as f:
|
148
|
+
config = json.load(f)
|
149
|
+
|
150
|
+
port = config.get("server", {}).get("port", 8000)
|
151
|
+
auth_enabled = config.get("security", {}).get("auth", {}).get("enabled", False)
|
152
|
+
auth_methods = config.get("security", {}).get("auth", {}).get("methods", [])
|
153
|
+
|
154
|
+
# Create test client with correct protocol
|
155
|
+
protocol = "https" if config.get("ssl", {}).get("enabled", False) else "http"
|
156
|
+
client = SecurityTestClient(base_url=f"{protocol}://localhost:{port}")
|
157
|
+
client.auth_enabled = auth_enabled
|
158
|
+
client.auth_methods = auth_methods
|
159
|
+
client.api_keys = config.get("security", {}).get("auth", {}).get("api_keys", {})
|
160
|
+
|
161
|
+
# For mTLS, override SSL context creation and change working directory
|
162
|
+
if config_name == "mtls":
|
163
|
+
client.create_ssl_context = client.create_ssl_context_for_mtls
|
164
|
+
# Ensure mTLS uses certificate auth
|
165
|
+
client.auth_methods = ["certificate"]
|
166
|
+
# Change to examples directory for mTLS tests
|
167
|
+
import os
|
168
|
+
os.chdir(self.project_root / "mcp_proxy_adapter" / "examples")
|
169
|
+
|
170
|
+
# Run tests
|
171
|
+
async with client:
|
172
|
+
# Test 1: Health check
|
173
|
+
result = await client.test_health()
|
174
|
+
results.append(result)
|
175
|
+
|
176
|
+
# Test 2: Command execution
|
177
|
+
result = await client.test_command_execution()
|
178
|
+
results.append(result)
|
179
|
+
|
180
|
+
# Test 3: Authentication (if enabled)
|
181
|
+
if auth_enabled:
|
182
|
+
result = await client.test_authentication()
|
183
|
+
results.append(result)
|
184
|
+
|
185
|
+
# Test 4: Negative authentication
|
186
|
+
result = await client.test_negative_authentication()
|
187
|
+
results.append(result)
|
188
|
+
|
189
|
+
# Test 5: Role-based access
|
190
|
+
result = await client.test_role_based_access(client.base_url, "api_key")
|
191
|
+
results.append(result)
|
192
|
+
|
193
|
+
# Test 6: Role permissions
|
194
|
+
result = await client.test_role_permissions(client.base_url, "api_key")
|
195
|
+
results.append(result)
|
196
|
+
|
197
|
+
# Test 7: Multiple roles test
|
198
|
+
result = await client.test_multiple_roles(client.base_url, "api_key")
|
199
|
+
results.append(result)
|
200
|
+
else:
|
201
|
+
# Test 3: No authentication required
|
202
|
+
result = await client.test_no_auth_required()
|
203
|
+
results.append(result)
|
204
|
+
|
205
|
+
# Test 4: Negative auth (should fail)
|
206
|
+
result = await client.test_negative_authentication()
|
207
|
+
results.append(result)
|
208
|
+
|
209
|
+
except Exception as e:
|
210
|
+
results.append(TestResult(
|
211
|
+
test_name=f"{config_name}_client_error",
|
212
|
+
server_url=f"http://localhost:{port}",
|
213
|
+
auth_type="none",
|
214
|
+
success=False,
|
215
|
+
error_message=str(e)
|
216
|
+
))
|
217
|
+
|
218
|
+
finally:
|
219
|
+
# Stop server
|
220
|
+
self.stop_server(config_name, process)
|
221
|
+
|
222
|
+
return results
|
223
|
+
|
224
|
+
async def run_all_tests(self):
|
225
|
+
"""Run all security tests."""
|
226
|
+
print("š Starting Security Testing Suite")
|
227
|
+
print("=" * 50)
|
228
|
+
|
229
|
+
# Test configurations
|
230
|
+
configs = [
|
231
|
+
("basic_http", "config_basic_http.json"),
|
232
|
+
("http_token", "config_http_token.json"),
|
233
|
+
("https", "config_https.json"),
|
234
|
+
("https_token", "config_https_token.json"),
|
235
|
+
("mtls", "config_mtls.json")
|
236
|
+
]
|
237
|
+
|
238
|
+
total_tests = 0
|
239
|
+
passed_tests = 0
|
240
|
+
|
241
|
+
for config_name, config_file in configs:
|
242
|
+
config_path = self.configs_dir / config_file
|
243
|
+
|
244
|
+
if not config_path.exists():
|
245
|
+
print(f"ā Configuration not found: {config_path}")
|
246
|
+
continue
|
247
|
+
|
248
|
+
print(f"\nš Testing {config_name.upper()} configuration")
|
249
|
+
print("-" * 30)
|
250
|
+
|
251
|
+
results = await self.test_server(config_name, config_path)
|
252
|
+
|
253
|
+
for result in results:
|
254
|
+
total_tests += 1
|
255
|
+
if result.success:
|
256
|
+
passed_tests += 1
|
257
|
+
print(f"ā
{result.test_name}: PASS")
|
258
|
+
else:
|
259
|
+
print(f"ā {result.test_name}: FAIL - {result.error_message}")
|
260
|
+
|
261
|
+
self.test_results.extend(results)
|
262
|
+
|
263
|
+
# Print summary
|
264
|
+
print("\n" + "=" * 50)
|
265
|
+
print("š TEST SUMMARY")
|
266
|
+
print("=" * 50)
|
267
|
+
print(f"Total tests: {total_tests}")
|
268
|
+
print(f"Passed: {passed_tests}")
|
269
|
+
print(f"Failed: {total_tests - passed_tests}")
|
270
|
+
print(f"Success rate: {(passed_tests/total_tests*100):.1f}%" if total_tests > 0 else "N/A")
|
271
|
+
|
272
|
+
# Detailed results
|
273
|
+
print("\nš DETAILED RESULTS")
|
274
|
+
print("-" * 30)
|
275
|
+
for result in self.test_results:
|
276
|
+
status = "ā
PASS" if result.success else "ā FAIL"
|
277
|
+
print(f"{status} {result.test_name}")
|
278
|
+
if not result.success and result.error_message:
|
279
|
+
print(f" Error: {result.error_message}")
|
280
|
+
|
281
|
+
return passed_tests == total_tests
|
282
|
+
|
283
|
+
|
284
|
+
async def main():
|
285
|
+
"""Main function."""
|
286
|
+
runner = SecurityTestRunner()
|
287
|
+
|
288
|
+
try:
|
289
|
+
success = await runner.run_all_tests()
|
290
|
+
sys.exit(0 if success else 1)
|
291
|
+
except KeyboardInterrupt:
|
292
|
+
print("\nā ļø Testing interrupted by user")
|
293
|
+
sys.exit(1)
|
294
|
+
except Exception as e:
|
295
|
+
print(f"\nā Testing failed: {e}")
|
296
|
+
sys.exit(1)
|
297
|
+
|
298
|
+
|
299
|
+
if __name__ == "__main__":
|
300
|
+
asyncio.run(main())
|