mcp-proxy-adapter 6.1.0__py3-none-any.whl ā 6.2.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 +27 -7
- mcp_proxy_adapter/api/app.py +18 -7
- mcp_proxy_adapter/api/middleware/__init__.py +2 -2
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +32 -13
- mcp_proxy_adapter/api/middleware/unified_security.py +12 -4
- mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
- mcp_proxy_adapter/core/app_factory.py +87 -3
- mcp_proxy_adapter/core/app_runner.py +272 -0
- mcp_proxy_adapter/core/certificate_utils.py +291 -73
- mcp_proxy_adapter/core/client.py +574 -0
- mcp_proxy_adapter/core/client_manager.py +284 -0
- mcp_proxy_adapter/core/protocol_manager.py +132 -10
- mcp_proxy_adapter/core/security_integration.py +19 -11
- mcp_proxy_adapter/core/server_adapter.py +17 -80
- mcp_proxy_adapter/core/server_engine.py +5 -99
- mcp_proxy_adapter/core/ssl_utils.py +13 -12
- mcp_proxy_adapter/core/transport_manager.py +5 -5
- mcp_proxy_adapter/examples/__init__.py +16 -0
- mcp_proxy_adapter/examples/basic_framework/__init__.py +7 -0
- mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +21 -40
- mcp_proxy_adapter/examples/commands/__init__.py +5 -1
- mcp_proxy_adapter/examples/create_certificates_simple.py +260 -75
- mcp_proxy_adapter/examples/debug_request_state.py +4 -36
- mcp_proxy_adapter/examples/debug_role_chain.py +2 -49
- mcp_proxy_adapter/examples/demo_client.py +0 -66
- mcp_proxy_adapter/examples/full_application/__init__.py +11 -0
- mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -19
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -16
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -22
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -24
- mcp_proxy_adapter/examples/full_application/main.py +65 -44
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +0 -67
- mcp_proxy_adapter/examples/generate_certificates.py +0 -15
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/generate_test_configs.py +204 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +3 -70
- mcp_proxy_adapter/examples/run_example.py +1 -23
- mcp_proxy_adapter/examples/run_security_tests.py +2 -60
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -53
- mcp_proxy_adapter/examples/security_test_client.py +18 -123
- mcp_proxy_adapter/examples/setup_test_environment.py +179 -0
- mcp_proxy_adapter/examples/test_config.py +148 -0
- mcp_proxy_adapter/examples/test_config_generator.py +86 -0
- mcp_proxy_adapter/examples/test_examples.py +4 -67
- mcp_proxy_adapter/examples/universal_client.py +154 -162
- mcp_proxy_adapter/main.py +51 -161
- mcp_proxy_adapter/utils/config_generator.py +90 -2
- mcp_proxy_adapter/version.py +4 -2
- mcp_proxy_adapter-6.2.0.dist-info/METADATA +687 -0
- mcp_proxy_adapter-6.2.0.dist-info/RECORD +122 -0
- mcp_proxy_adapter/examples/README.md +0 -257
- mcp_proxy_adapter/examples/README_EN.md +0 -258
- mcp_proxy_adapter/examples/SECURITY_TESTING.md +0 -455
- mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
- mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
- mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +0 -37
- mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +0 -23
- mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +0 -39
- mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +0 -25
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +0 -39
- mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +0 -45
- mcp_proxy_adapter/examples/basic_framework/roles.json +0 -21
- mcp_proxy_adapter/examples/cert_config.json +0 -9
- mcp_proxy_adapter/examples/certs/admin.crt +0 -32
- mcp_proxy_adapter/examples/certs/admin.key +0 -52
- mcp_proxy_adapter/examples/certs/admin_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/admin_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/ca_cert.pem +0 -23
- mcp_proxy_adapter/examples/certs/ca_cert.srl +0 -1
- mcp_proxy_adapter/examples/certs/ca_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/cert_config.json +0 -9
- mcp_proxy_adapter/examples/certs/client.crt +0 -32
- mcp_proxy_adapter/examples/certs/client.key +0 -52
- mcp_proxy_adapter/examples/certs/client_admin.crt +0 -32
- mcp_proxy_adapter/examples/certs/client_admin.key +0 -52
- mcp_proxy_adapter/examples/certs/client_user.crt +0 -32
- mcp_proxy_adapter/examples/certs/client_user.key +0 -52
- mcp_proxy_adapter/examples/certs/guest_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/guest_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +0 -23
- mcp_proxy_adapter/examples/certs/proxy_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/proxy_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/readonly.crt +0 -32
- mcp_proxy_adapter/examples/certs/readonly.key +0 -52
- mcp_proxy_adapter/examples/certs/readonly_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/readonly_key.pem +0 -28
- mcp_proxy_adapter/examples/certs/server.crt +0 -32
- mcp_proxy_adapter/examples/certs/server.key +0 -52
- mcp_proxy_adapter/examples/certs/server_cert.pem +0 -32
- mcp_proxy_adapter/examples/certs/server_key.pem +0 -52
- mcp_proxy_adapter/examples/certs/test_ca_ca.crt +0 -20
- mcp_proxy_adapter/examples/certs/user.crt +0 -32
- mcp_proxy_adapter/examples/certs/user.key +0 -52
- mcp_proxy_adapter/examples/certs/user_cert.pem +0 -21
- mcp_proxy_adapter/examples/certs/user_key.pem +0 -28
- mcp_proxy_adapter/examples/client_configs/api_key_client.json +0 -13
- mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +0 -13
- mcp_proxy_adapter/examples/client_configs/certificate_client.json +0 -22
- mcp_proxy_adapter/examples/client_configs/jwt_client.json +0 -15
- mcp_proxy_adapter/examples/client_configs/no_auth_client.json +0 -9
- mcp_proxy_adapter/examples/full_application/configs/http_auth.json +0 -37
- mcp_proxy_adapter/examples/full_application/configs/http_simple.json +0 -23
- mcp_proxy_adapter/examples/full_application/configs/https_auth.json +0 -39
- mcp_proxy_adapter/examples/full_application/configs/https_simple.json +0 -25
- mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +0 -39
- mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +0 -45
- mcp_proxy_adapter/examples/full_application/roles.json +0 -21
- mcp_proxy_adapter/examples/keys/ca_key.pem +0 -28
- mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +0 -28
- mcp_proxy_adapter/examples/keys/test_ca_ca.key +0 -28
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +0 -220
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +0 -220
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +0 -2
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +0 -1
- mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +0 -1
- mcp_proxy_adapter/examples/roles.json +0 -38
- mcp_proxy_adapter/examples/server_configs/config_basic_http.json +0 -204
- mcp_proxy_adapter/examples/server_configs/config_http_token.json +0 -238
- mcp_proxy_adapter/examples/server_configs/config_https.json +0 -215
- mcp_proxy_adapter/examples/server_configs/config_https_token.json +0 -231
- mcp_proxy_adapter/examples/server_configs/config_mtls.json +0 -215
- mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +0 -250
- mcp_proxy_adapter/examples/server_configs/config_simple.json +0 -46
- mcp_proxy_adapter/examples/server_configs/roles.json +0 -38
- mcp_proxy_adapter-6.1.0.dist-info/METADATA +0 -205
- mcp_proxy_adapter-6.1.0.dist-info/RECORD +0 -193
- {mcp_proxy_adapter-6.1.0.dist-info ā mcp_proxy_adapter-6.2.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.1.0.dist-info ā mcp_proxy_adapter-6.2.0.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.1.0.dist-info ā mcp_proxy_adapter-6.2.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-6.1.0.dist-info ā mcp_proxy_adapter-6.2.0.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,15 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
"""
|
3
3
|
Security Test Client for MCP Proxy Adapter
|
4
|
-
|
5
4
|
This client tests various security configurations including:
|
6
5
|
- Basic HTTP
|
7
6
|
- HTTP + Token authentication
|
8
7
|
- HTTPS
|
9
8
|
- HTTPS + Token authentication
|
10
9
|
- mTLS with certificate authentication
|
11
|
-
|
12
10
|
Author: Vasiliy Zdanovskiy
|
13
11
|
email: vasilyvz@gmail.com
|
14
12
|
"""
|
15
|
-
|
16
13
|
import asyncio
|
17
14
|
import json
|
18
15
|
import os
|
@@ -22,20 +19,15 @@ import time
|
|
22
19
|
from pathlib import Path
|
23
20
|
from typing import Dict, List, Optional, Any
|
24
21
|
from dataclasses import dataclass
|
25
|
-
|
26
22
|
import aiohttp
|
27
23
|
from aiohttp import ClientSession, ClientTimeout, TCPConnector
|
28
|
-
|
29
24
|
# Add project root to path for imports
|
30
25
|
project_root = Path(__file__).parent.parent.parent
|
31
26
|
current_dir = Path(__file__).parent
|
32
27
|
parent_dir = current_dir.parent
|
33
|
-
|
34
28
|
sys.path.insert(0, str(project_root))
|
35
29
|
sys.path.insert(0, str(current_dir))
|
36
30
|
sys.path.insert(0, str(parent_dir))
|
37
|
-
|
38
|
-
|
39
31
|
@dataclass
|
40
32
|
class TestResult:
|
41
33
|
"""Test result data class."""
|
@@ -47,11 +39,8 @@ class TestResult:
|
|
47
39
|
response_data: Optional[Dict] = None
|
48
40
|
error_message: Optional[str] = None
|
49
41
|
duration: float = 0.0
|
50
|
-
|
51
|
-
|
52
42
|
class SecurityTestClient:
|
53
43
|
"""Security test client for comprehensive testing."""
|
54
|
-
|
55
44
|
def __init__(self, base_url: str = "http://localhost:8000"):
|
56
45
|
"""Initialize security test client."""
|
57
46
|
self.base_url = base_url
|
@@ -62,7 +51,6 @@ class SecurityTestClient:
|
|
62
51
|
self.ssl_manager = None
|
63
52
|
self.auth_manager = None
|
64
53
|
self.test_results: List[TestResult] = []
|
65
|
-
|
66
54
|
# Test tokens
|
67
55
|
self.test_tokens = {
|
68
56
|
"admin": "test-token-123",
|
@@ -72,7 +60,6 @@ class SecurityTestClient:
|
|
72
60
|
"proxy": "proxy-token-123",
|
73
61
|
"invalid": "invalid-token-999"
|
74
62
|
}
|
75
|
-
|
76
63
|
# Test certificates
|
77
64
|
self.test_certificates = {
|
78
65
|
"admin": {
|
@@ -88,68 +75,51 @@ class SecurityTestClient:
|
|
88
75
|
"key": "mcp_proxy_adapter/examples/certs/readonly_key.pem"
|
89
76
|
}
|
90
77
|
}
|
91
|
-
|
92
78
|
async def __aenter__(self):
|
93
79
|
"""Async context manager entry."""
|
94
80
|
timeout = ClientTimeout(total=30)
|
95
|
-
|
96
81
|
# Create SSL context for HTTPS connections
|
97
82
|
ssl_context = self.create_ssl_context()
|
98
83
|
connector = TCPConnector(ssl=ssl_context)
|
99
|
-
|
100
84
|
self.session = ClientSession(timeout=timeout, connector=connector)
|
101
85
|
return self
|
102
|
-
|
103
86
|
def create_ssl_context_for_mtls(self) -> ssl.SSLContext:
|
104
87
|
"""Create SSL context for mTLS connections."""
|
105
88
|
ssl_context = ssl.create_default_context()
|
106
|
-
|
107
89
|
# For mTLS testing
|
108
90
|
ssl_context.check_hostname = False
|
109
91
|
ssl_context.verify_mode = ssl.CERT_NONE
|
110
|
-
|
111
92
|
# Load client certificate and key
|
112
93
|
cert_file = "./certs/user_cert.pem"
|
113
94
|
key_file = "./certs/user_key.pem"
|
114
95
|
ca_cert_file = "./certs/ca_cert.pem"
|
115
|
-
|
116
96
|
if os.path.exists(cert_file) and os.path.exists(key_file):
|
117
97
|
ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file)
|
118
|
-
|
119
98
|
if os.path.exists(ca_cert_file):
|
120
99
|
ssl_context.load_verify_locations(cafile=ca_cert_file)
|
121
|
-
|
122
100
|
return ssl_context
|
123
|
-
|
124
101
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
125
102
|
"""Async context manager exit."""
|
126
103
|
if self.session:
|
127
104
|
await self.session.close()
|
128
|
-
|
129
105
|
def create_ssl_context(self, cert_file: Optional[str] = None,
|
130
106
|
key_file: Optional[str] = None,
|
131
107
|
ca_cert_file: Optional[str] = None) -> ssl.SSLContext:
|
132
108
|
"""Create SSL context for client."""
|
133
109
|
ssl_context = ssl.create_default_context()
|
134
|
-
|
135
110
|
# For testing with self-signed certificates
|
136
111
|
ssl_context.check_hostname = False
|
137
112
|
ssl_context.verify_mode = ssl.CERT_NONE
|
138
|
-
|
139
113
|
if cert_file and key_file:
|
140
114
|
ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file)
|
141
|
-
|
142
115
|
if ca_cert_file:
|
143
116
|
ssl_context.load_verify_locations(cafile=ca_cert_file)
|
144
117
|
# For testing, still don't verify
|
145
118
|
ssl_context.verify_mode = ssl.CERT_NONE
|
146
|
-
|
147
119
|
return ssl_context
|
148
|
-
|
149
120
|
def create_auth_headers(self, auth_type: str, **kwargs) -> Dict[str, str]:
|
150
121
|
"""Create authentication headers."""
|
151
122
|
headers = {"Content-Type": "application/json"}
|
152
|
-
|
153
123
|
if auth_type == "api_key":
|
154
124
|
token = kwargs.get("token", "test-token-123") # Use correct token
|
155
125
|
headers["X-API-Key"] = token # Use X-API-Key header
|
@@ -163,20 +133,15 @@ class SecurityTestClient:
|
|
163
133
|
# For mTLS, we need to use client certificates
|
164
134
|
# This is handled by SSL context, not headers
|
165
135
|
pass
|
166
|
-
|
167
136
|
return headers
|
168
|
-
|
169
137
|
async def test_health_check(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
|
170
138
|
"""Test health check endpoint."""
|
171
139
|
start_time = time.time()
|
172
140
|
test_name = f"Health Check ({auth_type})"
|
173
|
-
|
174
141
|
try:
|
175
142
|
headers = self.create_auth_headers(auth_type, **kwargs)
|
176
|
-
|
177
143
|
async with self.session.get(f"{server_url}/health", headers=headers) as response:
|
178
144
|
duration = time.time() - start_time
|
179
|
-
|
180
145
|
if response.status == 200:
|
181
146
|
data = await response.json()
|
182
147
|
return TestResult(
|
@@ -199,7 +164,6 @@ class SecurityTestClient:
|
|
199
164
|
error_message=f"Health check failed: {error_text}",
|
200
165
|
duration=duration
|
201
166
|
)
|
202
|
-
|
203
167
|
except Exception as e:
|
204
168
|
duration = time.time() - start_time
|
205
169
|
return TestResult(
|
@@ -210,15 +174,12 @@ class SecurityTestClient:
|
|
210
174
|
error_message=f"Health check error: {str(e)}",
|
211
175
|
duration=duration
|
212
176
|
)
|
213
|
-
|
214
177
|
async def test_echo_command(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
|
215
178
|
"""Test echo command."""
|
216
179
|
start_time = time.time()
|
217
180
|
test_name = f"Echo Command ({auth_type})"
|
218
|
-
|
219
181
|
try:
|
220
182
|
headers = self.create_auth_headers(auth_type, **kwargs)
|
221
|
-
|
222
183
|
data = {
|
223
184
|
"jsonrpc": "2.0",
|
224
185
|
"method": "echo",
|
@@ -227,12 +188,10 @@ class SecurityTestClient:
|
|
227
188
|
},
|
228
189
|
"id": 1
|
229
190
|
}
|
230
|
-
|
231
|
-
|
232
|
-
headers=headers,
|
191
|
+
async with self.session.post(f"{server_url}/cmd",
|
192
|
+
headers=headers,
|
233
193
|
json=data) as response:
|
234
194
|
duration = time.time() - start_time
|
235
|
-
|
236
195
|
if response.status == 200:
|
237
196
|
data = await response.json()
|
238
197
|
return TestResult(
|
@@ -255,7 +214,6 @@ class SecurityTestClient:
|
|
255
214
|
error_message=f"Echo command failed: {error_text}",
|
256
215
|
duration=duration
|
257
216
|
)
|
258
|
-
|
259
217
|
except Exception as e:
|
260
218
|
duration = time.time() - start_time
|
261
219
|
return TestResult(
|
@@ -266,27 +224,22 @@ class SecurityTestClient:
|
|
266
224
|
error_message=f"Echo command error: {str(e)}",
|
267
225
|
duration=duration
|
268
226
|
)
|
269
|
-
|
270
227
|
async def test_security_command(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
|
271
228
|
"""Test security command."""
|
272
229
|
start_time = time.time()
|
273
230
|
test_name = f"Security Command ({auth_type})"
|
274
|
-
|
275
231
|
try:
|
276
232
|
headers = self.create_auth_headers(auth_type, **kwargs)
|
277
|
-
|
278
233
|
data = {
|
279
234
|
"jsonrpc": "2.0",
|
280
235
|
"method": "health",
|
281
236
|
"params": {},
|
282
237
|
"id": 2
|
283
238
|
}
|
284
|
-
|
285
|
-
|
286
|
-
headers=headers,
|
239
|
+
async with self.session.post(f"{server_url}/cmd",
|
240
|
+
headers=headers,
|
287
241
|
json=data) as response:
|
288
242
|
duration = time.time() - start_time
|
289
|
-
|
290
243
|
if response.status == 200:
|
291
244
|
data = await response.json()
|
292
245
|
return TestResult(
|
@@ -309,7 +262,6 @@ class SecurityTestClient:
|
|
309
262
|
error_message=f"Security command failed: {error_text}",
|
310
263
|
duration=duration
|
311
264
|
)
|
312
|
-
|
313
265
|
except Exception as e:
|
314
266
|
duration = time.time() - start_time
|
315
267
|
return TestResult(
|
@@ -320,15 +272,12 @@ class SecurityTestClient:
|
|
320
272
|
error_message=f"Security command error: {str(e)}",
|
321
273
|
duration=duration
|
322
274
|
)
|
323
|
-
|
324
275
|
async def test_health(self) -> TestResult:
|
325
276
|
"""Test health endpoint."""
|
326
277
|
return await self.test_health_check(self.base_url, "none")
|
327
|
-
|
328
278
|
async def test_command_execution(self) -> TestResult:
|
329
279
|
"""Test command execution."""
|
330
280
|
return await self.test_echo_command(self.base_url, "none")
|
331
|
-
|
332
281
|
async def test_authentication(self) -> TestResult:
|
333
282
|
"""Test authentication."""
|
334
283
|
if "api_key" in self.auth_methods:
|
@@ -346,38 +295,29 @@ class SecurityTestClient:
|
|
346
295
|
success=False,
|
347
296
|
error_message="No authentication method available"
|
348
297
|
)
|
349
|
-
|
350
298
|
async def test_negative_authentication(self) -> TestResult:
|
351
299
|
"""Test negative authentication (should fail)."""
|
352
300
|
return await self.test_echo_command(self.base_url, "api_key", token="invalid-token")
|
353
|
-
|
354
301
|
async def test_no_auth_required(self) -> TestResult:
|
355
302
|
"""Test that no authentication is required."""
|
356
303
|
return await self.test_echo_command(self.base_url, "none")
|
357
|
-
|
358
|
-
|
359
|
-
|
360
304
|
async def test_negative_auth(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
|
361
305
|
"""Test negative authentication scenarios."""
|
362
306
|
start_time = time.time()
|
363
307
|
test_name = f"Negative Auth ({auth_type})"
|
364
|
-
|
365
308
|
try:
|
366
309
|
# Use invalid token
|
367
310
|
headers = self.create_auth_headers("api_key", token="invalid-token-999")
|
368
|
-
|
369
311
|
data = {
|
370
312
|
"jsonrpc": "2.0",
|
371
313
|
"method": "echo",
|
372
314
|
"params": {"message": "Should fail"},
|
373
315
|
"id": 3
|
374
316
|
}
|
375
|
-
|
376
|
-
|
377
|
-
headers=headers,
|
317
|
+
async with self.session.post(f"{server_url}/cmd",
|
318
|
+
headers=headers,
|
378
319
|
json=data) as response:
|
379
320
|
duration = time.time() - start_time
|
380
|
-
|
381
321
|
# Expected to fail with 401
|
382
322
|
if response.status == 401:
|
383
323
|
return TestResult(
|
@@ -399,7 +339,6 @@ class SecurityTestClient:
|
|
399
339
|
error_message=f"Expected 401, got {response.status}",
|
400
340
|
duration=duration
|
401
341
|
)
|
402
|
-
|
403
342
|
except Exception as e:
|
404
343
|
duration = time.time() - start_time
|
405
344
|
return TestResult(
|
@@ -410,30 +349,25 @@ class SecurityTestClient:
|
|
410
349
|
error_message=f"Negative auth error: {str(e)}",
|
411
350
|
duration=duration
|
412
351
|
)
|
413
|
-
|
414
352
|
async def test_role_based_access(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
|
415
353
|
"""Test role-based access control."""
|
416
354
|
start_time = time.time()
|
417
355
|
test_name = f"Role-Based Access ({auth_type})"
|
418
|
-
|
419
356
|
try:
|
420
357
|
# Test with different roles
|
421
358
|
role = kwargs.get("role", "user")
|
422
359
|
token = self.test_tokens.get(role, self.test_tokens["user"])
|
423
360
|
headers = self.create_auth_headers("api_key", token=token)
|
424
|
-
|
425
361
|
data = {
|
426
362
|
"jsonrpc": "2.0",
|
427
363
|
"method": "echo",
|
428
364
|
"params": {"message": f"Testing {role} role"},
|
429
365
|
"id": 4
|
430
366
|
}
|
431
|
-
|
432
|
-
|
433
|
-
headers=headers,
|
367
|
+
async with self.session.post(f"{server_url}/cmd",
|
368
|
+
headers=headers,
|
434
369
|
json=data) as response:
|
435
370
|
duration = time.time() - start_time
|
436
|
-
|
437
371
|
if response.status == 200:
|
438
372
|
data = await response.json()
|
439
373
|
return TestResult(
|
@@ -456,7 +390,6 @@ class SecurityTestClient:
|
|
456
390
|
error_message=f"Role-based access failed: {error_text}",
|
457
391
|
duration=duration
|
458
392
|
)
|
459
|
-
|
460
393
|
except Exception as e:
|
461
394
|
duration = time.time() - start_time
|
462
395
|
return TestResult(
|
@@ -467,31 +400,26 @@ class SecurityTestClient:
|
|
467
400
|
error_message=f"Role-based access error: {str(e)}",
|
468
401
|
duration=duration
|
469
402
|
)
|
470
|
-
|
471
403
|
async def test_role_permissions(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
|
472
404
|
"""Test role permissions with role_test command."""
|
473
405
|
start_time = time.time()
|
474
406
|
test_name = f"Role Permissions Test ({auth_type})"
|
475
|
-
|
476
407
|
try:
|
477
408
|
# Test with different roles and actions
|
478
409
|
role = kwargs.get("role", "user")
|
479
410
|
action = kwargs.get("action", "read")
|
480
411
|
token = self.test_tokens.get(role, self.test_tokens["user"])
|
481
412
|
headers = self.create_auth_headers("api_key", token=token)
|
482
|
-
|
483
413
|
data = {
|
484
414
|
"jsonrpc": "2.0",
|
485
415
|
"method": "role_test",
|
486
416
|
"params": {"action": action},
|
487
417
|
"id": 5
|
488
418
|
}
|
489
|
-
|
490
|
-
|
491
|
-
headers=headers,
|
419
|
+
async with self.session.post(f"{server_url}/cmd",
|
420
|
+
headers=headers,
|
492
421
|
json=data) as response:
|
493
422
|
duration = time.time() - start_time
|
494
|
-
|
495
423
|
if response.status == 200:
|
496
424
|
data = await response.json()
|
497
425
|
return TestResult(
|
@@ -514,7 +442,6 @@ class SecurityTestClient:
|
|
514
442
|
error_message=f"Role permissions test failed: {error_text}",
|
515
443
|
duration=duration
|
516
444
|
)
|
517
|
-
|
518
445
|
except Exception as e:
|
519
446
|
duration = time.time() - start_time
|
520
447
|
return TestResult(
|
@@ -525,26 +452,22 @@ class SecurityTestClient:
|
|
525
452
|
error_message=f"Role permissions test error: {str(e)}",
|
526
453
|
duration=duration
|
527
454
|
)
|
528
|
-
|
529
455
|
async def test_multiple_roles(self, server_url: str, auth_type: str = "none", **kwargs) -> TestResult:
|
530
456
|
"""Test multiple roles with different permissions."""
|
531
457
|
start_time = time.time()
|
532
458
|
test_name = f"Multiple Roles Test ({auth_type})"
|
533
|
-
|
534
459
|
try:
|
535
460
|
# Test admin role (should have all permissions)
|
536
461
|
admin_token = self.test_tokens.get("admin", "admin-token-123")
|
537
462
|
admin_headers = self.create_auth_headers("api_key", token=admin_token)
|
538
|
-
|
539
463
|
admin_data = {
|
540
464
|
"jsonrpc": "2.0",
|
541
465
|
"method": "role_test",
|
542
466
|
"params": {"action": "manage"},
|
543
467
|
"id": 6
|
544
468
|
}
|
545
|
-
|
546
|
-
|
547
|
-
headers=admin_headers,
|
469
|
+
async with self.session.post(f"{server_url}/cmd",
|
470
|
+
headers=admin_headers,
|
548
471
|
json=admin_data) as response:
|
549
472
|
if response.status != 200:
|
550
473
|
return TestResult(
|
@@ -556,22 +479,18 @@ class SecurityTestClient:
|
|
556
479
|
error_message="Admin role test failed",
|
557
480
|
duration=time.time() - start_time
|
558
481
|
)
|
559
|
-
|
560
482
|
# Test readonly role (should only have read permission)
|
561
483
|
readonly_token = self.test_tokens.get("readonly", "readonly-token-123")
|
562
484
|
readonly_headers = self.create_auth_headers("api_key", token=readonly_token)
|
563
|
-
|
564
485
|
readonly_data = {
|
565
486
|
"jsonrpc": "2.0",
|
566
487
|
"method": "role_test",
|
567
488
|
"params": {"action": "write"},
|
568
489
|
}
|
569
|
-
|
570
|
-
|
571
|
-
headers=readonly_headers,
|
490
|
+
async with self.session.post(f"{server_url}/cmd",
|
491
|
+
headers=readonly_headers,
|
572
492
|
json=readonly_data) as response:
|
573
493
|
duration = time.time() - start_time
|
574
|
-
|
575
494
|
# Readonly should be denied write access
|
576
495
|
if response.status == 403:
|
577
496
|
return TestResult(
|
@@ -593,7 +512,6 @@ class SecurityTestClient:
|
|
593
512
|
error_message="Readonly role incorrectly allowed write access",
|
594
513
|
duration=duration
|
595
514
|
)
|
596
|
-
|
597
515
|
except Exception as e:
|
598
516
|
duration = time.time() - start_time
|
599
517
|
return TestResult(
|
@@ -604,12 +522,10 @@ class SecurityTestClient:
|
|
604
522
|
error_message=f"Multiple roles test error: {str(e)}",
|
605
523
|
duration=duration
|
606
524
|
)
|
607
|
-
|
608
525
|
async def run_security_tests(self, server_url: str, auth_type: str = "none", **kwargs) -> List[TestResult]:
|
609
526
|
"""Run comprehensive security tests."""
|
610
527
|
print(f"\nš Running security tests for {server_url} ({auth_type})")
|
611
528
|
print("=" * 60)
|
612
|
-
|
613
529
|
tests = [
|
614
530
|
self.test_health_check(server_url, auth_type, **kwargs),
|
615
531
|
self.test_echo_command(server_url, auth_type, **kwargs),
|
@@ -617,13 +533,11 @@ class SecurityTestClient:
|
|
617
533
|
self.test_negative_auth(server_url, auth_type, **kwargs),
|
618
534
|
self.test_role_based_access(server_url, auth_type, **kwargs)
|
619
535
|
]
|
620
|
-
|
621
536
|
results = []
|
622
537
|
for test in tests:
|
623
538
|
result = await test
|
624
539
|
results.append(result)
|
625
540
|
self.test_results.append(result)
|
626
|
-
|
627
541
|
# Print result
|
628
542
|
status = "ā
PASS" if result.success else "ā FAIL"
|
629
543
|
print(f"{status} {result.test_name}")
|
@@ -633,9 +547,7 @@ class SecurityTestClient:
|
|
633
547
|
if result.error_message:
|
634
548
|
print(f" Error: {result.error_message}")
|
635
549
|
print()
|
636
|
-
|
637
550
|
return results
|
638
|
-
|
639
551
|
async def test_all_scenarios(self) -> Dict[str, List[TestResult]]:
|
640
552
|
"""Test all security scenarios."""
|
641
553
|
scenarios = {
|
@@ -660,41 +572,32 @@ class SecurityTestClient:
|
|
660
572
|
"auth": "certificate"
|
661
573
|
}
|
662
574
|
}
|
663
|
-
|
664
575
|
all_results = {}
|
665
|
-
|
666
576
|
for scenario_name, config in scenarios.items():
|
667
577
|
print(f"\nš Testing scenario: {scenario_name.upper()}")
|
668
578
|
print("=" * 60)
|
669
|
-
|
670
579
|
try:
|
671
580
|
results = await self.run_security_tests(
|
672
|
-
config["url"],
|
581
|
+
config["url"],
|
673
582
|
config["auth"]
|
674
583
|
)
|
675
584
|
all_results[scenario_name] = results
|
676
|
-
|
677
585
|
except Exception as e:
|
678
586
|
print(f"ā Failed to test {scenario_name}: {e}")
|
679
587
|
all_results[scenario_name] = []
|
680
|
-
|
681
588
|
return all_results
|
682
|
-
|
683
589
|
def print_summary(self):
|
684
590
|
"""Print test summary."""
|
685
591
|
print("\n" + "=" * 80)
|
686
592
|
print("š SECURITY TEST SUMMARY")
|
687
593
|
print("=" * 80)
|
688
|
-
|
689
594
|
total_tests = len(self.test_results)
|
690
595
|
passed_tests = sum(1 for r in self.test_results if r.success)
|
691
596
|
failed_tests = total_tests - passed_tests
|
692
|
-
|
693
597
|
print(f"Total Tests: {total_tests}")
|
694
598
|
print(f"Passed: {passed_tests} ā
")
|
695
599
|
print(f"Failed: {failed_tests} ā")
|
696
600
|
print(f"Success Rate: {(passed_tests/total_tests*100):.1f}%")
|
697
|
-
|
698
601
|
if failed_tests > 0:
|
699
602
|
print("\nā Failed Tests:")
|
700
603
|
for result in self.test_results:
|
@@ -702,31 +605,25 @@ class SecurityTestClient:
|
|
702
605
|
print(f" - {result.test_name} ({result.server_url})")
|
703
606
|
if result.error_message:
|
704
607
|
print(f" Error: {result.error_message}")
|
705
|
-
|
706
608
|
print("\nā
Passed Tests:")
|
707
609
|
for result in self.test_results:
|
708
610
|
if result.success:
|
709
611
|
print(f" - {result.test_name} ({result.server_url})")
|
710
|
-
|
711
|
-
|
712
612
|
async def main():
|
713
613
|
"""Main function."""
|
714
614
|
import argparse
|
715
|
-
|
716
615
|
parser = argparse.ArgumentParser(description="Security Test Client for MCP Proxy Adapter")
|
717
|
-
parser.add_argument("--server", default="http://localhost:8000",
|
616
|
+
parser.add_argument("--server", default="http://localhost:8000",
|
718
617
|
help="Server URL to test")
|
719
|
-
parser.add_argument("--auth", choices=["none", "api_key", "basic", "certificate"],
|
618
|
+
parser.add_argument("--auth", choices=["none", "api_key", "basic", "certificate"],
|
720
619
|
default="none", help="Authentication type")
|
721
|
-
parser.add_argument("--all-scenarios", action="store_true",
|
620
|
+
parser.add_argument("--all-scenarios", action="store_true",
|
722
621
|
help="Test all security scenarios")
|
723
622
|
parser.add_argument("--token", help="API token for authentication")
|
724
623
|
parser.add_argument("--cert", help="Client certificate file")
|
725
624
|
parser.add_argument("--key", help="Client private key file")
|
726
625
|
parser.add_argument("--ca-cert", help="CA certificate file")
|
727
|
-
|
728
626
|
args = parser.parse_args()
|
729
|
-
|
730
627
|
if args.all_scenarios:
|
731
628
|
# Test all scenarios
|
732
629
|
async with SecurityTestClient() as client:
|
@@ -737,7 +634,5 @@ async def main():
|
|
737
634
|
async with SecurityTestClient(args.server) as client:
|
738
635
|
await client.run_security_tests(args.server, args.auth, token=args.token)
|
739
636
|
client.print_summary()
|
740
|
-
|
741
|
-
|
742
637
|
if __name__ == "__main__":
|
743
638
|
asyncio.run(main())
|