mcp-proxy-adapter 6.9.28__py3-none-any.whl ā 6.9.29__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.
Potentially problematic release.
This version of mcp-proxy-adapter might be problematic. Click here for more details.
- mcp_proxy_adapter/__init__.py +10 -0
- mcp_proxy_adapter/__main__.py +8 -21
- mcp_proxy_adapter/api/app.py +10 -913
- mcp_proxy_adapter/api/core/__init__.py +18 -0
- mcp_proxy_adapter/api/core/app_factory.py +243 -0
- mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
- mcp_proxy_adapter/api/core/registration_manager.py +166 -0
- mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
- mcp_proxy_adapter/api/handlers.py +78 -199
- mcp_proxy_adapter/api/middleware/__init__.py +1 -44
- mcp_proxy_adapter/api/middleware/base.py +0 -42
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
- mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
- mcp_proxy_adapter/api/middleware/factory.py +0 -94
- mcp_proxy_adapter/api/middleware/logging.py +0 -112
- mcp_proxy_adapter/api/middleware/performance.py +0 -35
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
- mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
- mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
- mcp_proxy_adapter/api/openapi/__init__.py +21 -0
- mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
- mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
- mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
- mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
- mcp_proxy_adapter/api/schemas.py +0 -61
- mcp_proxy_adapter/api/tool_integration.py +0 -117
- mcp_proxy_adapter/api/tools.py +0 -46
- mcp_proxy_adapter/cli/__init__.py +12 -0
- mcp_proxy_adapter/cli/commands/__init__.py +15 -0
- mcp_proxy_adapter/cli/commands/client.py +100 -0
- mcp_proxy_adapter/cli/commands/config_generate.py +21 -0
- mcp_proxy_adapter/cli/commands/config_validate.py +36 -0
- mcp_proxy_adapter/cli/commands/generate.py +259 -0
- mcp_proxy_adapter/cli/commands/server.py +174 -0
- mcp_proxy_adapter/cli/commands/sets.py +128 -0
- mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
- mcp_proxy_adapter/cli/examples/__init__.py +8 -0
- mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
- mcp_proxy_adapter/cli/examples/https_token.py +96 -0
- mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
- mcp_proxy_adapter/cli/main.py +63 -0
- mcp_proxy_adapter/cli/parser.py +324 -0
- mcp_proxy_adapter/cli/validators.py +231 -0
- mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
- mcp_proxy_adapter/client/proxy.py +45 -0
- mcp_proxy_adapter/commands/__init__.py +44 -28
- mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
- mcp_proxy_adapter/commands/base.py +19 -43
- mcp_proxy_adapter/commands/builtin_commands.py +0 -75
- mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
- mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
- mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
- mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
- mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
- mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
- mcp_proxy_adapter/commands/catalog_manager.py +58 -928
- mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
- mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
- mcp_proxy_adapter/commands/command_registry.py +172 -904
- mcp_proxy_adapter/commands/config_command.py +0 -28
- mcp_proxy_adapter/commands/dependency_container.py +1 -70
- mcp_proxy_adapter/commands/dependency_manager.py +0 -128
- mcp_proxy_adapter/commands/echo_command.py +0 -34
- mcp_proxy_adapter/commands/health_command.py +0 -3
- mcp_proxy_adapter/commands/help_command.py +0 -159
- mcp_proxy_adapter/commands/hooks.py +0 -137
- mcp_proxy_adapter/commands/key_management_command.py +0 -25
- mcp_proxy_adapter/commands/load_command.py +7 -78
- mcp_proxy_adapter/commands/plugins_command.py +0 -16
- mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
- mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
- mcp_proxy_adapter/commands/queue_commands.py +750 -0
- mcp_proxy_adapter/commands/registration_status_command.py +0 -43
- mcp_proxy_adapter/commands/registry/__init__.py +18 -0
- mcp_proxy_adapter/commands/registry/command_info.py +103 -0
- mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
- mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
- mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
- mcp_proxy_adapter/commands/reload_command.py +0 -80
- mcp_proxy_adapter/commands/result.py +25 -77
- mcp_proxy_adapter/commands/role_test_command.py +0 -44
- mcp_proxy_adapter/commands/roles_management_command.py +0 -199
- mcp_proxy_adapter/commands/security_command.py +0 -30
- mcp_proxy_adapter/commands/settings_command.py +0 -68
- mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
- mcp_proxy_adapter/commands/token_management_command.py +0 -1
- mcp_proxy_adapter/commands/transport_management_command.py +0 -20
- mcp_proxy_adapter/commands/unload_command.py +0 -71
- mcp_proxy_adapter/config.py +15 -626
- mcp_proxy_adapter/core/__init__.py +5 -39
- mcp_proxy_adapter/core/app_factory.py +14 -36
- mcp_proxy_adapter/core/app_runner.py +0 -27
- mcp_proxy_adapter/core/auth_validator.py +1 -93
- mcp_proxy_adapter/core/certificate/__init__.py +20 -0
- mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
- mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
- mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
- mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
- mcp_proxy_adapter/core/certificate_utils.py +64 -903
- mcp_proxy_adapter/core/client.py +0 -6
- mcp_proxy_adapter/core/client_manager.py +0 -19
- mcp_proxy_adapter/core/client_security.py +0 -2
- mcp_proxy_adapter/core/config/__init__.py +18 -0
- mcp_proxy_adapter/core/config/config.py +195 -0
- mcp_proxy_adapter/core/config/config_factory.py +22 -0
- mcp_proxy_adapter/core/config/config_loader.py +66 -0
- mcp_proxy_adapter/core/config/feature_manager.py +31 -0
- mcp_proxy_adapter/core/config/simple_config.py +112 -0
- mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
- mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
- mcp_proxy_adapter/core/config_converter.py +0 -186
- mcp_proxy_adapter/core/config_validator.py +96 -1238
- mcp_proxy_adapter/core/errors.py +7 -42
- mcp_proxy_adapter/core/job_manager.py +54 -0
- mcp_proxy_adapter/core/logging.py +2 -22
- mcp_proxy_adapter/core/mtls_asgi.py +0 -20
- mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
- mcp_proxy_adapter/core/mtls_proxy.py +0 -80
- mcp_proxy_adapter/core/mtls_server.py +3 -173
- mcp_proxy_adapter/core/protocol_manager.py +1 -191
- mcp_proxy_adapter/core/proxy/__init__.py +22 -0
- mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
- mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
- mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
- mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
- mcp_proxy_adapter/core/proxy_client.py +0 -1
- mcp_proxy_adapter/core/proxy_registration.py +36 -913
- mcp_proxy_adapter/core/role_utils.py +0 -308
- mcp_proxy_adapter/core/security_adapter.py +1 -36
- mcp_proxy_adapter/core/security_factory.py +1 -150
- mcp_proxy_adapter/core/security_integration.py +0 -33
- mcp_proxy_adapter/core/server_adapter.py +1 -40
- mcp_proxy_adapter/core/server_engine.py +2 -173
- mcp_proxy_adapter/core/settings.py +0 -127
- mcp_proxy_adapter/core/signal_handler.py +0 -65
- mcp_proxy_adapter/core/ssl_utils.py +19 -137
- mcp_proxy_adapter/core/transport_manager.py +0 -151
- mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
- mcp_proxy_adapter/core/utils.py +1 -182
- mcp_proxy_adapter/core/validation/__init__.py +21 -0
- mcp_proxy_adapter/core/validation/config_validator.py +211 -0
- mcp_proxy_adapter/core/validation/file_validator.py +73 -0
- mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
- mcp_proxy_adapter/core/validation/security_validator.py +58 -0
- mcp_proxy_adapter/core/validation/validation_result.py +27 -0
- mcp_proxy_adapter/custom_openapi.py +33 -652
- mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
- mcp_proxy_adapter/examples/check_config.py +0 -2
- mcp_proxy_adapter/examples/client_usage_example.py +164 -0
- mcp_proxy_adapter/examples/config_builder.py +13 -2
- mcp_proxy_adapter/examples/config_cli.py +0 -1
- mcp_proxy_adapter/examples/create_test_configs.py +0 -46
- mcp_proxy_adapter/examples/debug_request_state.py +0 -1
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
- mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
- mcp_proxy_adapter/examples/full_application/main.py +186 -150
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
- mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
- mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
- mcp_proxy_adapter/examples/generate_config.py +65 -11
- mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
- mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
- mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
- mcp_proxy_adapter/examples/queue_server_example.py +85 -0
- mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
- mcp_proxy_adapter/examples/required_certificates.py +0 -2
- mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
- mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
- mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
- mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
- mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
- mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
- mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
- mcp_proxy_adapter/examples/security_test_client.py +24 -1075
- mcp_proxy_adapter/examples/setup/__init__.py +24 -0
- mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
- mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
- mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
- mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
- mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
- mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +133 -1425
- mcp_proxy_adapter/examples/test_config.py +0 -3
- mcp_proxy_adapter/examples/test_config_builder.py +25 -405
- mcp_proxy_adapter/examples/test_examples.py +0 -1
- mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
- mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
- mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
- mcp_proxy_adapter/examples/universal_client.py +0 -6
- mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
- mcp_proxy_adapter/integrations/__init__.py +25 -0
- mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
- mcp_proxy_adapter/main.py +70 -62
- mcp_proxy_adapter/openapi.py +0 -22
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.9.28.dist-info ā mcp_proxy_adapter-6.9.29.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.9.29.dist-info/RECORD +235 -0
- {mcp_proxy_adapter-6.9.28.dist-info ā mcp_proxy_adapter-6.9.29.dist-info}/entry_points.txt +1 -1
- mcp_proxy_adapter-6.9.28.dist-info/RECORD +0 -149
- {mcp_proxy_adapter-6.9.28.dist-info ā mcp_proxy_adapter-6.9.29.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.9.28.dist-info ā mcp_proxy_adapter-6.9.29.dist-info}/top_level.txt +0 -0
|
@@ -7,32 +7,23 @@ This client tests various security configurations including:
|
|
|
7
7
|
- HTTPS
|
|
8
8
|
- HTTPS + Token authentication
|
|
9
9
|
- mTLS with certificate authentication
|
|
10
|
+
|
|
10
11
|
Author: Vasiliy Zdanovskiy
|
|
11
12
|
email: vasilyvz@gmail.com
|
|
12
13
|
"""
|
|
13
14
|
import asyncio
|
|
14
|
-
import os
|
|
15
|
-
import ssl
|
|
16
15
|
import sys
|
|
17
|
-
import time
|
|
18
16
|
from pathlib import Path
|
|
19
|
-
from typing import Dict, List, Optional
|
|
20
|
-
from dataclasses import dataclass
|
|
21
|
-
from aiohttp import ClientSession, ClientTimeout, TCPConnector
|
|
22
17
|
|
|
23
18
|
# Add project root to path for imports
|
|
24
19
|
current_dir = Path(__file__).parent
|
|
25
20
|
parent_dir = current_dir.parent
|
|
26
|
-
# Only add paths that are likely to exist and be useful
|
|
27
21
|
if parent_dir.exists():
|
|
28
22
|
sys.path.insert(0, str(parent_dir))
|
|
29
23
|
sys.path.insert(0, str(current_dir))
|
|
30
24
|
|
|
31
25
|
# Import mcp_security_framework components
|
|
32
26
|
try:
|
|
33
|
-
from mcp_security_framework import SecurityManager, SecurityConfig, AuthConfig, PermissionConfig, SSLConfig
|
|
34
|
-
from mcp_security_framework.schemas.config import SSLConfig as SSLConfigSchema
|
|
35
|
-
|
|
36
27
|
_MCP_SECURITY_AVAILABLE = True
|
|
37
28
|
print("ā
mcp_security_framework available")
|
|
38
29
|
except ImportError as e:
|
|
@@ -49,1074 +40,32 @@ except ImportError:
|
|
|
49
40
|
_CRYPTOGRAPHY_AVAILABLE = False
|
|
50
41
|
print("ā ļø cryptography not available, SSL validation will be limited")
|
|
51
42
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class TestResult:
|
|
55
|
-
"""Test result data class."""
|
|
56
|
-
|
|
57
|
-
test_name: str
|
|
58
|
-
server_url: str
|
|
59
|
-
auth_type: str
|
|
60
|
-
success: bool
|
|
61
|
-
status_code: Optional[int] = None
|
|
62
|
-
response_data: Optional[Dict] = None
|
|
63
|
-
error_message: Optional[str] = None
|
|
64
|
-
duration: float = 0.0
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class SecurityTestClient:
|
|
68
|
-
"""Security test client for comprehensive testing."""
|
|
69
|
-
|
|
70
|
-
def __init__(self, base_url: str = "http://localhost:8000"):
|
|
71
|
-
"""Initialize security test client."""
|
|
72
|
-
self.base_url = base_url
|
|
73
|
-
self.session: Optional[ClientSession] = None
|
|
74
|
-
|
|
75
|
-
# Initialize security managers if available
|
|
76
|
-
self.ssl_manager = None
|
|
77
|
-
self.cert_manager = None
|
|
78
|
-
self._security_available = _MCP_SECURITY_AVAILABLE
|
|
79
|
-
self._crypto_available = _CRYPTOGRAPHY_AVAILABLE
|
|
80
|
-
|
|
81
|
-
# Authentication configuration
|
|
82
|
-
self.auth_enabled = False
|
|
83
|
-
self.auth_methods = []
|
|
84
|
-
self.api_keys = {}
|
|
85
|
-
self.roles_file = None
|
|
86
|
-
self.roles = {}
|
|
87
|
-
|
|
88
|
-
if self._security_available:
|
|
89
|
-
# For testing purposes, we don't initialize SecurityManager
|
|
90
|
-
# because we're testing server configurations, not the framework itself
|
|
91
|
-
# SecurityManager will be used only when actually needed by the server
|
|
92
|
-
self.ssl_manager = None
|
|
93
|
-
print("ā
mcp_security_framework available for testing")
|
|
94
|
-
|
|
95
|
-
if not self._security_available:
|
|
96
|
-
print("ā¹ļø Using standard SSL library for testing")
|
|
97
|
-
self.ssl_manager = None
|
|
98
|
-
self.test_results: List[TestResult] = []
|
|
99
|
-
# Test tokens
|
|
100
|
-
self.test_tokens = {
|
|
101
|
-
"admin": "admin-secret-key",
|
|
102
|
-
"user": "user-secret-key",
|
|
103
|
-
"readonly": "readonly-secret-key",
|
|
104
|
-
"guest": "guest-token-123",
|
|
105
|
-
"proxy": "proxy-token-123",
|
|
106
|
-
"invalid": "invalid-token-999",
|
|
107
|
-
}
|
|
108
|
-
# Test certificates - use relative paths
|
|
109
|
-
self.test_certificates = {
|
|
110
|
-
"admin": {
|
|
111
|
-
"cert": "certs/admin_cert.pem",
|
|
112
|
-
"key": "certs/admin_key.pem",
|
|
113
|
-
},
|
|
114
|
-
"user": {
|
|
115
|
-
"cert": "certs/user_cert.pem",
|
|
116
|
-
"key": "keys/user_key.pem",
|
|
117
|
-
},
|
|
118
|
-
"readonly": {
|
|
119
|
-
"cert": "certs/readonly_cert.pem",
|
|
120
|
-
"key": "certs/readonly_key.pem",
|
|
121
|
-
},
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
def create_ssl_context_for_mtls(self) -> ssl.SSLContext:
|
|
125
|
-
"""Create SSL context for mTLS connections."""
|
|
126
|
-
# For mTLS, we need client certificates - check if they exist
|
|
127
|
-
# Use absolute paths to avoid issues with working directory
|
|
128
|
-
# Use newly generated admin_client_client.crt which is signed by the same CA as server
|
|
129
|
-
project_root = Path(__file__).parent.parent.parent
|
|
130
|
-
cert_file = str(project_root / "certs" / "admin_client_client.crt")
|
|
131
|
-
key_file = str(project_root / "keys" / "admin-client_client.key")
|
|
132
|
-
ca_cert_file = str(project_root / "certs" / "localhost_server.crt")
|
|
133
|
-
|
|
134
|
-
# CRITICAL: For mTLS, certificates are REQUIRED
|
|
135
|
-
if not os.path.exists(cert_file):
|
|
136
|
-
raise FileNotFoundError(f"CRITICAL ERROR: mTLS client certificate not found: {cert_file}")
|
|
137
|
-
if not os.path.exists(key_file):
|
|
138
|
-
raise FileNotFoundError(f"CRITICAL ERROR: mTLS client key not found: {key_file}")
|
|
139
|
-
|
|
140
|
-
# For testing, we use standard SSL library
|
|
141
|
-
# SecurityManager is not initialized for testing purposes
|
|
142
|
-
|
|
143
|
-
# Use standard SSL library for testing
|
|
144
|
-
ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
|
145
|
-
# For mTLS testing - client needs to present certificate to server
|
|
146
|
-
ssl_context.check_hostname = False
|
|
147
|
-
ssl_context.verify_mode = ssl.CERT_NONE # Don't verify server cert for testing
|
|
148
|
-
# Load client certificate and key for mTLS (we already checked they exist)
|
|
149
|
-
ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file)
|
|
150
|
-
if os.path.exists(ca_cert_file):
|
|
151
|
-
ssl_context.load_verify_locations(cafile=ca_cert_file)
|
|
152
|
-
return ssl_context
|
|
153
|
-
|
|
154
|
-
async def __aenter__(self):
|
|
155
|
-
"""Async context manager entry."""
|
|
156
|
-
timeout = ClientTimeout(total=30)
|
|
157
|
-
# Create SSL context only for HTTPS URLs
|
|
158
|
-
if self.base_url.startswith('https://'):
|
|
159
|
-
# Check if this is mTLS (ports 20006, 20007, 20008 are mTLS test ports)
|
|
160
|
-
if any(port in self.base_url for port in ['20006', '20007', '20008']):
|
|
161
|
-
# Use mTLS context with client certificates
|
|
162
|
-
ssl_context = self.create_ssl_context_for_mtls()
|
|
163
|
-
else:
|
|
164
|
-
# Use regular HTTPS context
|
|
165
|
-
ssl_context = self.create_ssl_context()
|
|
166
|
-
connector = TCPConnector(ssl=ssl_context)
|
|
167
|
-
else:
|
|
168
|
-
# For HTTP URLs, use default connector without SSL
|
|
169
|
-
connector = TCPConnector()
|
|
170
|
-
|
|
171
|
-
# Create session
|
|
172
|
-
self.session = ClientSession(timeout=timeout, connector=connector)
|
|
173
|
-
return self
|
|
174
|
-
|
|
175
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
176
|
-
"""Async context manager exit."""
|
|
177
|
-
if self.session:
|
|
178
|
-
await self.session.close()
|
|
179
|
-
|
|
180
|
-
def create_ssl_context(
|
|
181
|
-
self,
|
|
182
|
-
cert_file: Optional[str] = None,
|
|
183
|
-
key_file: Optional[str] = None,
|
|
184
|
-
ca_cert_file: Optional[str] = None,
|
|
185
|
-
) -> ssl.SSLContext:
|
|
186
|
-
"""Create SSL context for client."""
|
|
187
|
-
# If certificates are provided, they must exist
|
|
188
|
-
if cert_file and not os.path.exists(cert_file):
|
|
189
|
-
raise FileNotFoundError(f"CRITICAL ERROR: SSL certificate not found: {cert_file}")
|
|
190
|
-
if key_file and not os.path.exists(key_file):
|
|
191
|
-
raise FileNotFoundError(f"CRITICAL ERROR: SSL key not found: {key_file}")
|
|
192
|
-
if ca_cert_file and not os.path.exists(ca_cert_file):
|
|
193
|
-
raise FileNotFoundError(f"CRITICAL ERROR: SSL CA certificate not found: {ca_cert_file}")
|
|
194
|
-
|
|
195
|
-
# For testing, we use standard SSL library
|
|
196
|
-
# SecurityManager is not initialized for testing purposes
|
|
197
|
-
|
|
198
|
-
# Use standard SSL library for testing
|
|
199
|
-
ssl_context = ssl.create_default_context()
|
|
200
|
-
# For testing with self-signed certificates
|
|
201
|
-
ssl_context.check_hostname = False
|
|
202
|
-
ssl_context.verify_mode = ssl.CERT_NONE
|
|
203
|
-
if cert_file and key_file:
|
|
204
|
-
ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file)
|
|
205
|
-
if ca_cert_file:
|
|
206
|
-
ssl_context.load_verify_locations(cafile=ca_cert_file)
|
|
207
|
-
# For testing, still don't verify
|
|
208
|
-
ssl_context.verify_mode = ssl.CERT_NONE
|
|
209
|
-
return ssl_context
|
|
210
|
-
|
|
211
|
-
def create_auth_headers(self, auth_type: str, **kwargs) -> Dict[str, str]:
|
|
212
|
-
"""Create authentication headers."""
|
|
213
|
-
headers = {"Content-Type": "application/json"}
|
|
214
|
-
if auth_type == "api_key":
|
|
215
|
-
token = kwargs.get("token")
|
|
216
|
-
if not token:
|
|
217
|
-
raise ValueError("token is required for api_key authentication")
|
|
218
|
-
print(f"š DEBUG: Using token: {token}")
|
|
219
|
-
# Provide both common header styles to maximize compatibility
|
|
220
|
-
headers["X-API-Key"] = token
|
|
221
|
-
headers["Authorization"] = f"Bearer {token}"
|
|
222
|
-
|
|
223
|
-
# Add role header if provided
|
|
224
|
-
role = kwargs.get("role")
|
|
225
|
-
if role:
|
|
226
|
-
headers["X-Role"] = role
|
|
227
|
-
elif auth_type == "basic":
|
|
228
|
-
username = kwargs.get("username")
|
|
229
|
-
password = kwargs.get("password")
|
|
230
|
-
if not username or not password:
|
|
231
|
-
raise ValueError("username and password are required for basic authentication")
|
|
232
|
-
import base64
|
|
233
|
-
|
|
234
|
-
credentials = base64.b64encode(f"{username}:{password}".encode()).decode()
|
|
235
|
-
headers["Authorization"] = f"Basic {credentials}"
|
|
236
|
-
elif auth_type == "certificate":
|
|
237
|
-
# For mTLS, we need to use client certificates
|
|
238
|
-
# This is handled by SSL context, not headers
|
|
239
|
-
pass
|
|
240
|
-
return headers
|
|
241
|
-
|
|
242
|
-
async def test_health_check(
|
|
243
|
-
self, server_url: str, auth_type: str = "none", **kwargs
|
|
244
|
-
) -> TestResult:
|
|
245
|
-
"""Test health check endpoint."""
|
|
246
|
-
start_time = time.time()
|
|
247
|
-
test_name = f"Health Check ({auth_type})"
|
|
248
|
-
try:
|
|
249
|
-
headers = self.create_auth_headers(auth_type, **kwargs)
|
|
250
|
-
async with self.session.get(
|
|
251
|
-
f"{server_url}/health", headers=headers
|
|
252
|
-
) as response:
|
|
253
|
-
duration = time.time() - start_time
|
|
254
|
-
if response.status == 200:
|
|
255
|
-
data = await response.json()
|
|
256
|
-
return TestResult(
|
|
257
|
-
test_name=test_name,
|
|
258
|
-
server_url=server_url,
|
|
259
|
-
auth_type=auth_type,
|
|
260
|
-
success=True,
|
|
261
|
-
status_code=response.status,
|
|
262
|
-
response_data=data,
|
|
263
|
-
duration=duration,
|
|
264
|
-
)
|
|
265
|
-
else:
|
|
266
|
-
error_text = await response.text()
|
|
267
|
-
return TestResult(
|
|
268
|
-
test_name=test_name,
|
|
269
|
-
server_url=server_url,
|
|
270
|
-
auth_type=auth_type,
|
|
271
|
-
success=False,
|
|
272
|
-
status_code=response.status,
|
|
273
|
-
error_message=f"Health check failed: {error_text}",
|
|
274
|
-
duration=duration,
|
|
275
|
-
)
|
|
276
|
-
except Exception as e:
|
|
277
|
-
duration = time.time() - start_time
|
|
278
|
-
return TestResult(
|
|
279
|
-
test_name=test_name,
|
|
280
|
-
server_url=server_url,
|
|
281
|
-
auth_type=auth_type,
|
|
282
|
-
success=False,
|
|
283
|
-
error_message=f"Health check error: {str(e)}",
|
|
284
|
-
duration=duration,
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
async def test_echo_command(
|
|
288
|
-
self, server_url: str, auth_type: str = "none", **kwargs
|
|
289
|
-
) -> TestResult:
|
|
290
|
-
"""Test echo command."""
|
|
291
|
-
start_time = time.time()
|
|
292
|
-
test_name = f"Echo Command ({auth_type})"
|
|
293
|
-
try:
|
|
294
|
-
headers = self.create_auth_headers(auth_type, **kwargs)
|
|
295
|
-
data = {
|
|
296
|
-
"jsonrpc": "2.0",
|
|
297
|
-
"method": "echo",
|
|
298
|
-
"params": {"message": "Hello from security test client!"},
|
|
299
|
-
"id": 1,
|
|
300
|
-
}
|
|
301
|
-
async with self.session.post(
|
|
302
|
-
f"{server_url}/cmd", headers=headers, json=data
|
|
303
|
-
) as response:
|
|
304
|
-
duration = time.time() - start_time
|
|
305
|
-
if response.status == 200:
|
|
306
|
-
data = await response.json()
|
|
307
|
-
return TestResult(
|
|
308
|
-
test_name=test_name,
|
|
309
|
-
server_url=server_url,
|
|
310
|
-
auth_type=auth_type,
|
|
311
|
-
success=True,
|
|
312
|
-
status_code=response.status,
|
|
313
|
-
response_data=data,
|
|
314
|
-
duration=duration,
|
|
315
|
-
)
|
|
316
|
-
else:
|
|
317
|
-
error_text = await response.text()
|
|
318
|
-
return TestResult(
|
|
319
|
-
test_name=test_name,
|
|
320
|
-
server_url=server_url,
|
|
321
|
-
auth_type=auth_type,
|
|
322
|
-
success=False,
|
|
323
|
-
status_code=response.status,
|
|
324
|
-
error_message=f"Echo command failed: {error_text}",
|
|
325
|
-
duration=duration,
|
|
326
|
-
)
|
|
327
|
-
except Exception as e:
|
|
328
|
-
duration = time.time() - start_time
|
|
329
|
-
return TestResult(
|
|
330
|
-
test_name=test_name,
|
|
331
|
-
server_url=server_url,
|
|
332
|
-
auth_type=auth_type,
|
|
333
|
-
success=False,
|
|
334
|
-
error_message=f"Echo command error: {str(e)}",
|
|
335
|
-
duration=duration,
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
async def test_security_command(
|
|
339
|
-
self, server_url: str, auth_type: str = "none", **kwargs
|
|
340
|
-
) -> TestResult:
|
|
341
|
-
"""Test security command."""
|
|
342
|
-
start_time = time.time()
|
|
343
|
-
test_name = f"Security Command ({auth_type})"
|
|
344
|
-
try:
|
|
345
|
-
headers = self.create_auth_headers(auth_type, **kwargs)
|
|
346
|
-
data = {"jsonrpc": "2.0", "method": "health", "params": {}, "id": 2}
|
|
347
|
-
async with self.session.post(
|
|
348
|
-
f"{server_url}/cmd", headers=headers, json=data
|
|
349
|
-
) as response:
|
|
350
|
-
duration = time.time() - start_time
|
|
351
|
-
if response.status == 200:
|
|
352
|
-
data = await response.json()
|
|
353
|
-
return TestResult(
|
|
354
|
-
test_name=test_name,
|
|
355
|
-
server_url=server_url,
|
|
356
|
-
auth_type=auth_type,
|
|
357
|
-
success=True,
|
|
358
|
-
status_code=response.status,
|
|
359
|
-
response_data=data,
|
|
360
|
-
duration=duration,
|
|
361
|
-
)
|
|
362
|
-
else:
|
|
363
|
-
error_text = await response.text()
|
|
364
|
-
return TestResult(
|
|
365
|
-
test_name=test_name,
|
|
366
|
-
server_url=server_url,
|
|
367
|
-
auth_type=auth_type,
|
|
368
|
-
success=False,
|
|
369
|
-
status_code=response.status,
|
|
370
|
-
error_message=f"Security command failed: {error_text}",
|
|
371
|
-
duration=duration,
|
|
372
|
-
)
|
|
373
|
-
except Exception as e:
|
|
374
|
-
duration = time.time() - start_time
|
|
375
|
-
return TestResult(
|
|
376
|
-
test_name=test_name,
|
|
377
|
-
server_url=server_url,
|
|
378
|
-
auth_type=auth_type,
|
|
379
|
-
success=False,
|
|
380
|
-
error_message=f"Security command error: {str(e)}",
|
|
381
|
-
duration=duration,
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
async def test_health(self) -> TestResult:
|
|
385
|
-
"""Test health endpoint."""
|
|
386
|
-
return await self.test_health_check(self.base_url, "none")
|
|
387
|
-
|
|
388
|
-
async def test_command_execution(self) -> TestResult:
|
|
389
|
-
"""Test command execution."""
|
|
390
|
-
return await self.test_echo_command(self.base_url, "none")
|
|
391
|
-
|
|
392
|
-
async def test_authentication(self) -> TestResult:
|
|
393
|
-
"""Test authentication."""
|
|
394
|
-
if "api_key" in self.auth_methods:
|
|
395
|
-
# Use admin API key value, not the key name
|
|
396
|
-
api_key_value = self.api_keys.get("admin", "admin-secret-key")
|
|
397
|
-
return await self.test_echo_command(self.base_url, "api_key", token=api_key_value)
|
|
398
|
-
elif "certificate" in self.auth_methods:
|
|
399
|
-
# For certificate auth, test with client certificate
|
|
400
|
-
return await self.test_echo_command(self.base_url, "certificate")
|
|
401
|
-
else:
|
|
402
|
-
return TestResult(
|
|
403
|
-
test_name="Authentication Test",
|
|
404
|
-
server_url=self.base_url,
|
|
405
|
-
auth_type="none",
|
|
406
|
-
success=False,
|
|
407
|
-
error_message="No authentication method available",
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
async def test_negative_authentication(self) -> TestResult:
|
|
411
|
-
"""Test negative authentication (should fail)."""
|
|
412
|
-
start_time = time.time()
|
|
413
|
-
test_name = "Negative Authentication Test"
|
|
414
|
-
try:
|
|
415
|
-
headers = self.create_auth_headers("api_key", token="invalid-token")
|
|
416
|
-
data = {
|
|
417
|
-
"jsonrpc": "2.0",
|
|
418
|
-
"method": "echo",
|
|
419
|
-
"params": {"message": "Should fail with invalid token"},
|
|
420
|
-
"id": 1,
|
|
421
|
-
}
|
|
422
|
-
async with self.session.post(
|
|
423
|
-
f"{self.base_url}/cmd", headers=headers, json=data
|
|
424
|
-
) as response:
|
|
425
|
-
duration = time.time() - start_time
|
|
426
|
-
|
|
427
|
-
# Check if API key authentication is enabled
|
|
428
|
-
api_key_auth_enabled = self.auth_enabled and "api_key" in self.auth_methods
|
|
429
|
-
|
|
430
|
-
if api_key_auth_enabled:
|
|
431
|
-
# For configurations with API key auth, 401 is expected (success)
|
|
432
|
-
if response.status == 401:
|
|
433
|
-
return TestResult(
|
|
434
|
-
test_name=test_name,
|
|
435
|
-
server_url=self.base_url,
|
|
436
|
-
auth_type="api_key",
|
|
437
|
-
success=True,
|
|
438
|
-
status_code=response.status,
|
|
439
|
-
response_data={"expected": "authentication_failure"},
|
|
440
|
-
duration=duration,
|
|
441
|
-
)
|
|
442
|
-
else:
|
|
443
|
-
return TestResult(
|
|
444
|
-
test_name=test_name,
|
|
445
|
-
server_url=self.base_url,
|
|
446
|
-
auth_type="api_key",
|
|
447
|
-
success=False,
|
|
448
|
-
status_code=response.status,
|
|
449
|
-
error_message=f"Expected 401 Unauthorized, got {response.status}",
|
|
450
|
-
duration=duration,
|
|
451
|
-
)
|
|
452
|
-
else:
|
|
453
|
-
# For configurations without API key auth, 200 is expected (success)
|
|
454
|
-
if response.status == 200:
|
|
455
|
-
return TestResult(
|
|
456
|
-
test_name=test_name,
|
|
457
|
-
server_url=self.base_url,
|
|
458
|
-
auth_type="api_key",
|
|
459
|
-
success=True,
|
|
460
|
-
status_code=response.status,
|
|
461
|
-
response_data={"expected": "no_auth_required"},
|
|
462
|
-
duration=duration,
|
|
463
|
-
)
|
|
464
|
-
else:
|
|
465
|
-
return TestResult(
|
|
466
|
-
test_name=test_name,
|
|
467
|
-
server_url=self.base_url,
|
|
468
|
-
auth_type="api_key",
|
|
469
|
-
success=False,
|
|
470
|
-
status_code=response.status,
|
|
471
|
-
error_message=f"Expected 200 OK (no auth required), got {response.status}",
|
|
472
|
-
duration=duration,
|
|
473
|
-
)
|
|
474
|
-
except Exception as e:
|
|
475
|
-
duration = time.time() - start_time
|
|
476
|
-
return TestResult(
|
|
477
|
-
test_name=test_name,
|
|
478
|
-
server_url=self.base_url,
|
|
479
|
-
auth_type="api_key",
|
|
480
|
-
success=False,
|
|
481
|
-
error_message=f"Negative auth test error: {str(e)}",
|
|
482
|
-
duration=duration,
|
|
483
|
-
)
|
|
484
|
-
|
|
485
|
-
async def test_no_auth_required(self) -> TestResult:
|
|
486
|
-
"""Test that no authentication is required."""
|
|
487
|
-
return await self.test_echo_command(self.base_url, "none")
|
|
488
|
-
|
|
489
|
-
async def test_negative_auth(
|
|
490
|
-
self, server_url: str, auth_type: str = "none", **kwargs
|
|
491
|
-
) -> TestResult:
|
|
492
|
-
"""Test negative authentication scenarios."""
|
|
493
|
-
start_time = time.time()
|
|
494
|
-
test_name = f"Negative Auth ({auth_type})"
|
|
495
|
-
try:
|
|
496
|
-
if auth_type == "certificate":
|
|
497
|
-
# For mTLS, test with invalid/expired certificate or no certificate
|
|
498
|
-
from aiohttp import ClientTimeout, TCPConnector
|
|
499
|
-
|
|
500
|
-
# Create SSL context with wrong certificate (should be rejected)
|
|
501
|
-
ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
|
502
|
-
ssl_context.check_hostname = False
|
|
503
|
-
# Don't load any client certificate - this should cause rejection
|
|
504
|
-
# Load CA certificate for server verification
|
|
505
|
-
ca_cert_file = "./certs/mcp_proxy_adapter_ca_ca.crt"
|
|
506
|
-
if os.path.exists(ca_cert_file):
|
|
507
|
-
ssl_context.load_verify_locations(cafile=ca_cert_file)
|
|
508
|
-
ssl_context.verify_mode = (
|
|
509
|
-
ssl.CERT_NONE
|
|
510
|
-
) # Don't verify server cert for testing
|
|
511
|
-
|
|
512
|
-
connector = TCPConnector(ssl=ssl_context)
|
|
513
|
-
timeout = ClientTimeout(total=10) # Shorter timeout
|
|
514
|
-
|
|
515
|
-
try:
|
|
516
|
-
import aiohttp
|
|
517
|
-
|
|
518
|
-
async with aiohttp.ClientSession(
|
|
519
|
-
timeout=timeout, connector=connector
|
|
520
|
-
) as temp_session:
|
|
521
|
-
data = {
|
|
522
|
-
"jsonrpc": "2.0",
|
|
523
|
-
"method": "echo",
|
|
524
|
-
"params": {"message": "Should fail without certificate"},
|
|
525
|
-
"id": 3,
|
|
526
|
-
}
|
|
527
|
-
async with temp_session.post(
|
|
528
|
-
f"{server_url}/cmd", json=data
|
|
529
|
-
) as response:
|
|
530
|
-
duration = time.time() - start_time
|
|
531
|
-
# If we get here, the server accepted the connection without proper certificate
|
|
532
|
-
# This is actually a security issue - server should reject
|
|
533
|
-
return TestResult(
|
|
534
|
-
test_name=test_name,
|
|
535
|
-
server_url=server_url,
|
|
536
|
-
auth_type=auth_type,
|
|
537
|
-
success=False,
|
|
538
|
-
status_code=response.status,
|
|
539
|
-
error_message=f"SECURITY ISSUE: mTLS server accepted connection without client certificate (status: {response.status})",
|
|
540
|
-
duration=duration,
|
|
541
|
-
)
|
|
542
|
-
except (Exception,) as e:
|
|
543
|
-
# This is expected - server should reject connections without proper certificate
|
|
544
|
-
duration = time.time() - start_time
|
|
545
|
-
return TestResult(
|
|
546
|
-
test_name=test_name,
|
|
547
|
-
server_url=server_url,
|
|
548
|
-
auth_type=auth_type,
|
|
549
|
-
success=True,
|
|
550
|
-
status_code=0,
|
|
551
|
-
response_data={
|
|
552
|
-
"expected": "connection_rejected",
|
|
553
|
-
"error": str(e),
|
|
554
|
-
},
|
|
555
|
-
duration=duration,
|
|
556
|
-
)
|
|
557
|
-
else:
|
|
558
|
-
# For other auth types, use invalid token
|
|
559
|
-
headers = self.create_auth_headers("api_key", token="invalid-token-999")
|
|
560
|
-
data = {
|
|
561
|
-
"jsonrpc": "2.0",
|
|
562
|
-
"method": "echo",
|
|
563
|
-
"params": {"message": "Should fail"},
|
|
564
|
-
"id": 3,
|
|
565
|
-
}
|
|
566
|
-
async with self.session.post(
|
|
567
|
-
f"{server_url}/cmd", headers=headers, json=data
|
|
568
|
-
) as response:
|
|
569
|
-
duration = time.time() - start_time
|
|
570
|
-
# Expect 401 only when auth is enforced
|
|
571
|
-
expects_auth = auth_type in ("api_key", "certificate", "basic")
|
|
572
|
-
if expects_auth and response.status == 401:
|
|
573
|
-
return TestResult(
|
|
574
|
-
test_name=test_name,
|
|
575
|
-
server_url=server_url,
|
|
576
|
-
auth_type=auth_type,
|
|
577
|
-
success=True,
|
|
578
|
-
status_code=response.status,
|
|
579
|
-
response_data={"expected": "authentication_failure"},
|
|
580
|
-
duration=duration,
|
|
581
|
-
)
|
|
582
|
-
elif not expects_auth and response.status == 200:
|
|
583
|
-
# Security disabled: negative auth should not fail
|
|
584
|
-
return TestResult(
|
|
585
|
-
test_name=test_name,
|
|
586
|
-
server_url=server_url,
|
|
587
|
-
auth_type=auth_type,
|
|
588
|
-
success=True,
|
|
589
|
-
status_code=response.status,
|
|
590
|
-
response_data={"expected": "no_auth_required"},
|
|
591
|
-
duration=duration,
|
|
592
|
-
)
|
|
593
|
-
else:
|
|
594
|
-
return TestResult(
|
|
595
|
-
test_name=test_name,
|
|
596
|
-
server_url=server_url,
|
|
597
|
-
auth_type=auth_type,
|
|
598
|
-
success=False,
|
|
599
|
-
status_code=response.status,
|
|
600
|
-
error_message=f"Unexpected status for negative auth: {response.status}",
|
|
601
|
-
duration=duration,
|
|
602
|
-
)
|
|
603
|
-
except Exception as e:
|
|
604
|
-
duration = time.time() - start_time
|
|
605
|
-
return TestResult(
|
|
606
|
-
test_name=test_name,
|
|
607
|
-
server_url=server_url,
|
|
608
|
-
auth_type=auth_type,
|
|
609
|
-
success=False,
|
|
610
|
-
error_message=f"Negative auth error: {str(e)}",
|
|
611
|
-
duration=duration,
|
|
612
|
-
)
|
|
613
|
-
|
|
614
|
-
async def test_role_based_access(
|
|
615
|
-
self, server_url: str, auth_type: str = "none", **kwargs
|
|
616
|
-
) -> TestResult:
|
|
617
|
-
"""Test role-based access control."""
|
|
618
|
-
start_time = time.time()
|
|
619
|
-
test_name = f"Role-Based Access ({auth_type})"
|
|
620
|
-
try:
|
|
621
|
-
# Test with different roles
|
|
622
|
-
role = kwargs.get("role")
|
|
623
|
-
if not role:
|
|
624
|
-
raise ValueError("role is required for role-based access test")
|
|
625
|
-
token = self.test_tokens.get(role)
|
|
626
|
-
if not token:
|
|
627
|
-
raise ValueError(f"token for role '{role}' is not configured")
|
|
628
|
-
headers = self.create_auth_headers("api_key", token=token)
|
|
629
|
-
data = {
|
|
630
|
-
"jsonrpc": "2.0",
|
|
631
|
-
"method": "echo",
|
|
632
|
-
"params": {"message": f"Testing {role} role"},
|
|
633
|
-
"id": 4,
|
|
634
|
-
}
|
|
635
|
-
async with self.session.post(
|
|
636
|
-
f"{server_url}/cmd", headers=headers, json=data
|
|
637
|
-
) as response:
|
|
638
|
-
duration = time.time() - start_time
|
|
639
|
-
if response.status == 200:
|
|
640
|
-
data = await response.json()
|
|
641
|
-
return TestResult(
|
|
642
|
-
test_name=test_name,
|
|
643
|
-
server_url=server_url,
|
|
644
|
-
auth_type=auth_type,
|
|
645
|
-
success=True,
|
|
646
|
-
status_code=response.status,
|
|
647
|
-
response_data=data,
|
|
648
|
-
duration=duration,
|
|
649
|
-
)
|
|
650
|
-
else:
|
|
651
|
-
error_text = await response.text()
|
|
652
|
-
return TestResult(
|
|
653
|
-
test_name=test_name,
|
|
654
|
-
server_url=server_url,
|
|
655
|
-
auth_type=auth_type,
|
|
656
|
-
success=False,
|
|
657
|
-
status_code=response.status,
|
|
658
|
-
error_message=f"Role-based access failed: {error_text}",
|
|
659
|
-
duration=duration,
|
|
660
|
-
)
|
|
661
|
-
except Exception as e:
|
|
662
|
-
duration = time.time() - start_time
|
|
663
|
-
return TestResult(
|
|
664
|
-
test_name=test_name,
|
|
665
|
-
server_url=server_url,
|
|
666
|
-
auth_type=auth_type,
|
|
667
|
-
success=False,
|
|
668
|
-
error_message=f"Role-based access error: {str(e)}",
|
|
669
|
-
duration=duration,
|
|
670
|
-
)
|
|
671
|
-
|
|
672
|
-
async def test_role_permissions(
|
|
673
|
-
self, server_url: str, auth_type: str = "none", **kwargs
|
|
674
|
-
) -> TestResult:
|
|
675
|
-
"""Test role permissions with role_test command."""
|
|
676
|
-
start_time = time.time()
|
|
677
|
-
test_name = f"Role Permissions Test ({auth_type})"
|
|
678
|
-
try:
|
|
679
|
-
# Test with different roles and actions
|
|
680
|
-
role = kwargs.get("role")
|
|
681
|
-
action = kwargs.get("action")
|
|
682
|
-
if not role:
|
|
683
|
-
raise ValueError("role is required for role permissions test")
|
|
684
|
-
if not action:
|
|
685
|
-
raise ValueError("action is required for role permissions test")
|
|
686
|
-
token = self.test_tokens.get(role)
|
|
687
|
-
if not token:
|
|
688
|
-
raise ValueError(f"token for role '{role}' is not configured")
|
|
689
|
-
headers = self.create_auth_headers("api_key", token=token)
|
|
690
|
-
data = {
|
|
691
|
-
"jsonrpc": "2.0",
|
|
692
|
-
"method": "role_test",
|
|
693
|
-
"params": {"action": action},
|
|
694
|
-
"id": 5,
|
|
695
|
-
}
|
|
696
|
-
async with self.session.post(
|
|
697
|
-
f"{server_url}/cmd", headers=headers, json=data
|
|
698
|
-
) as response:
|
|
699
|
-
duration = time.time() - start_time
|
|
700
|
-
if response.status == 200:
|
|
701
|
-
data = await response.json()
|
|
702
|
-
return TestResult(
|
|
703
|
-
test_name=test_name,
|
|
704
|
-
server_url=server_url,
|
|
705
|
-
auth_type=auth_type,
|
|
706
|
-
success=True,
|
|
707
|
-
status_code=response.status,
|
|
708
|
-
response_data=data,
|
|
709
|
-
duration=duration,
|
|
710
|
-
)
|
|
711
|
-
else:
|
|
712
|
-
error_text = await response.text()
|
|
713
|
-
return TestResult(
|
|
714
|
-
test_name=test_name,
|
|
715
|
-
server_url=server_url,
|
|
716
|
-
auth_type=auth_type,
|
|
717
|
-
success=False,
|
|
718
|
-
status_code=response.status,
|
|
719
|
-
error_message=f"Role permissions test failed: {error_text}",
|
|
720
|
-
duration=duration,
|
|
721
|
-
)
|
|
722
|
-
except Exception as e:
|
|
723
|
-
duration = time.time() - start_time
|
|
724
|
-
return TestResult(
|
|
725
|
-
test_name=test_name,
|
|
726
|
-
server_url=server_url,
|
|
727
|
-
auth_type=auth_type,
|
|
728
|
-
success=False,
|
|
729
|
-
error_message=f"Role permissions test error: {str(e)}",
|
|
730
|
-
duration=duration,
|
|
731
|
-
)
|
|
732
|
-
|
|
733
|
-
async def test_multiple_roles(
|
|
734
|
-
self, server_url: str, auth_type: str = "none", **kwargs
|
|
735
|
-
) -> TestResult:
|
|
736
|
-
"""Test multiple roles with different permissions."""
|
|
737
|
-
start_time = time.time()
|
|
738
|
-
test_name = f"Multiple Roles Test ({auth_type})"
|
|
739
|
-
try:
|
|
740
|
-
# Test admin role (should have all permissions)
|
|
741
|
-
admin_token = self.test_tokens.get("admin")
|
|
742
|
-
if not admin_token:
|
|
743
|
-
raise ValueError("admin token is not configured")
|
|
744
|
-
admin_headers = self.create_auth_headers("api_key", token=admin_token)
|
|
745
|
-
admin_data = {
|
|
746
|
-
"jsonrpc": "2.0",
|
|
747
|
-
"method": "role_test",
|
|
748
|
-
"params": {"action": "manage"},
|
|
749
|
-
"id": 6,
|
|
750
|
-
}
|
|
751
|
-
async with self.session.post(
|
|
752
|
-
f"{server_url}/cmd", headers=admin_headers, json=admin_data
|
|
753
|
-
) as response:
|
|
754
|
-
if response.status != 200:
|
|
755
|
-
return TestResult(
|
|
756
|
-
test_name=test_name,
|
|
757
|
-
server_url=server_url,
|
|
758
|
-
auth_type=auth_type,
|
|
759
|
-
success=False,
|
|
760
|
-
status_code=response.status,
|
|
761
|
-
error_message="Admin role test failed",
|
|
762
|
-
duration=time.time() - start_time,
|
|
763
|
-
)
|
|
764
|
-
# Test readonly role (should only have read permission)
|
|
765
|
-
readonly_token = self.test_tokens.get("readonly")
|
|
766
|
-
if not readonly_token:
|
|
767
|
-
raise ValueError("readonly token is not configured")
|
|
768
|
-
readonly_headers = self.create_auth_headers(
|
|
769
|
-
"api_key", token=readonly_token
|
|
770
|
-
)
|
|
771
|
-
readonly_data = {
|
|
772
|
-
"jsonrpc": "2.0",
|
|
773
|
-
"method": "role_test",
|
|
774
|
-
"params": {"action": "write"},
|
|
775
|
-
}
|
|
776
|
-
async with self.session.post(
|
|
777
|
-
f"{server_url}/cmd", headers=readonly_headers, json=readonly_data
|
|
778
|
-
) as response:
|
|
779
|
-
duration = time.time() - start_time
|
|
780
|
-
# Readonly should be denied write access
|
|
781
|
-
if response.status == 403:
|
|
782
|
-
return TestResult(
|
|
783
|
-
test_name=test_name,
|
|
784
|
-
server_url=server_url,
|
|
785
|
-
auth_type=auth_type,
|
|
786
|
-
success=True,
|
|
787
|
-
status_code=response.status,
|
|
788
|
-
response_data={
|
|
789
|
-
"message": "Correctly denied write access to readonly role"
|
|
790
|
-
},
|
|
791
|
-
duration=duration,
|
|
792
|
-
)
|
|
793
|
-
else:
|
|
794
|
-
return TestResult(
|
|
795
|
-
test_name=test_name,
|
|
796
|
-
server_url=server_url,
|
|
797
|
-
auth_type=auth_type,
|
|
798
|
-
success=False,
|
|
799
|
-
status_code=response.status,
|
|
800
|
-
error_message="Readonly role incorrectly allowed write access",
|
|
801
|
-
duration=duration,
|
|
802
|
-
)
|
|
803
|
-
except Exception as e:
|
|
804
|
-
duration = time.time() - start_time
|
|
805
|
-
return TestResult(
|
|
806
|
-
test_name=test_name,
|
|
807
|
-
server_url=server_url,
|
|
808
|
-
auth_type=auth_type,
|
|
809
|
-
success=False,
|
|
810
|
-
error_message=f"Multiple roles test error: {str(e)}",
|
|
811
|
-
duration=duration,
|
|
812
|
-
)
|
|
813
|
-
|
|
814
|
-
async def run_security_tests(
|
|
815
|
-
self, server_url: str, auth_type: str = "none", **kwargs
|
|
816
|
-
) -> List[TestResult]:
|
|
817
|
-
"""Run comprehensive security tests."""
|
|
818
|
-
print(f"\nš Running security tests for {server_url} ({auth_type})")
|
|
819
|
-
print("=" * 60)
|
|
820
|
-
tests = [
|
|
821
|
-
self.test_health_check(server_url, auth_type, **kwargs),
|
|
822
|
-
self.test_echo_command(server_url, auth_type, **kwargs),
|
|
823
|
-
self.test_security_command(server_url, auth_type, **kwargs),
|
|
824
|
-
self.test_negative_auth(server_url, auth_type, **kwargs),
|
|
825
|
-
self.test_role_based_access(server_url, auth_type, role="admin", **kwargs),
|
|
826
|
-
]
|
|
827
|
-
results = []
|
|
828
|
-
for test in tests:
|
|
829
|
-
result = await test
|
|
830
|
-
results.append(result)
|
|
831
|
-
self.test_results.append(result)
|
|
832
|
-
# Print result
|
|
833
|
-
status = "ā
PASS" if result.success else "ā FAIL"
|
|
834
|
-
print(f"{status} {result.test_name}")
|
|
835
|
-
print(f" Duration: {result.duration:.3f}s")
|
|
836
|
-
if result.status_code:
|
|
837
|
-
print(f" Status: {result.status_code}")
|
|
838
|
-
if result.error_message:
|
|
839
|
-
print(f" Error: {result.error_message}")
|
|
840
|
-
print()
|
|
841
|
-
return results
|
|
842
|
-
|
|
843
|
-
async def test_all_scenarios(self) -> Dict[str, List[TestResult]]:
|
|
844
|
-
"""Test all security scenarios."""
|
|
845
|
-
scenarios = {
|
|
846
|
-
"basic_http": {"url": "http://localhost:8000", "auth": "none"},
|
|
847
|
-
"http_token": {"url": "http://localhost:8001", "auth": "api_key"},
|
|
848
|
-
"https": {"url": "https://localhost:8443", "auth": "none"},
|
|
849
|
-
"https_token": {"url": "https://localhost:8444", "auth": "api_key"},
|
|
850
|
-
"mtls": {"url": "https://localhost:8445", "auth": "certificate"},
|
|
851
|
-
}
|
|
852
|
-
all_results = {}
|
|
853
|
-
for scenario_name, config in scenarios.items():
|
|
854
|
-
print(f"\nš Testing scenario: {scenario_name.upper()}")
|
|
855
|
-
print("=" * 60)
|
|
856
|
-
try:
|
|
857
|
-
results = await self.run_security_tests(config["url"], config["auth"])
|
|
858
|
-
all_results[scenario_name] = results
|
|
859
|
-
except Exception as e:
|
|
860
|
-
print(f"ā Failed to test {scenario_name}: {e}")
|
|
861
|
-
all_results[scenario_name] = []
|
|
862
|
-
return all_results
|
|
863
|
-
|
|
864
|
-
def print_summary(self):
|
|
865
|
-
"""Print test summary."""
|
|
866
|
-
print("\n" + "=" * 80)
|
|
867
|
-
print("š SECURITY TEST SUMMARY")
|
|
868
|
-
print("=" * 80)
|
|
869
|
-
total_tests = len(self.test_results)
|
|
870
|
-
passed_tests = sum(1 for r in self.test_results if r.success)
|
|
871
|
-
failed_tests = total_tests - passed_tests
|
|
872
|
-
print(f"Total Tests: {total_tests}")
|
|
873
|
-
print(f"Passed: {passed_tests} ā
")
|
|
874
|
-
print(f"Failed: {failed_tests} ā")
|
|
875
|
-
print(f"Success Rate: {(passed_tests / total_tests * 100):.1f}%")
|
|
876
|
-
if failed_tests > 0:
|
|
877
|
-
print("\nā Failed Tests:")
|
|
878
|
-
for result in self.test_results:
|
|
879
|
-
if not result.success:
|
|
880
|
-
print(f" - {result.test_name} ({result.server_url})")
|
|
881
|
-
if result.error_message:
|
|
882
|
-
print(f" Error: {result.error_message}")
|
|
883
|
-
print("\nā
Passed Tests:")
|
|
884
|
-
for result in self.test_results:
|
|
885
|
-
if result.success:
|
|
886
|
-
print(f" - {result.test_name} ({result.server_url})")
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
async def test_health(self) -> TestResult:
|
|
890
|
-
"""Test health check endpoint."""
|
|
891
|
-
return await self.test_health_check(self.base_url, "none")
|
|
892
|
-
|
|
893
|
-
async def test_command_execution(self) -> TestResult:
|
|
894
|
-
"""Test command execution."""
|
|
895
|
-
if self.auth_enabled and "api_key" in self.auth_methods:
|
|
896
|
-
# Use admin API key value, not the key name
|
|
897
|
-
api_key_value = self.api_keys.get("admin", "admin-secret-key")
|
|
898
|
-
return await self.test_echo_command(self.base_url, "api_key", token=api_key_value)
|
|
899
|
-
else:
|
|
900
|
-
return await self.test_echo_command(self.base_url, "none")
|
|
901
|
-
|
|
902
|
-
async def test_authentication(self) -> TestResult:
|
|
903
|
-
"""Test authentication."""
|
|
904
|
-
if "api_key" in self.auth_methods:
|
|
905
|
-
# Use admin API key value, not the key name
|
|
906
|
-
api_key_value = self.api_keys.get("admin", "admin-secret-key")
|
|
907
|
-
return await self.test_echo_command(self.base_url, "api_key", token=api_key_value)
|
|
908
|
-
elif "certificate" in self.auth_methods:
|
|
909
|
-
# For certificate auth, test with client certificate
|
|
910
|
-
return await self.test_echo_command(self.base_url, "certificate")
|
|
911
|
-
else:
|
|
912
|
-
return TestResult(
|
|
913
|
-
test_name="Authentication Test",
|
|
914
|
-
server_url=self.base_url,
|
|
915
|
-
auth_type="none",
|
|
916
|
-
success=False,
|
|
917
|
-
error_message="No authentication method available",
|
|
918
|
-
)
|
|
919
|
-
|
|
920
|
-
async def test_negative_authentication(self) -> TestResult:
|
|
921
|
-
"""Test negative authentication (should fail)."""
|
|
922
|
-
start_time = time.time()
|
|
923
|
-
test_name = "Negative Authentication Test"
|
|
924
|
-
try:
|
|
925
|
-
headers = self.create_auth_headers("api_key", token="invalid-token")
|
|
926
|
-
data = {
|
|
927
|
-
"jsonrpc": "2.0",
|
|
928
|
-
"method": "echo",
|
|
929
|
-
"params": {"message": "Should fail with invalid token"},
|
|
930
|
-
"id": 1,
|
|
931
|
-
}
|
|
932
|
-
async with self.session.post(
|
|
933
|
-
f"{self.base_url}/cmd", headers=headers, json=data
|
|
934
|
-
) as response:
|
|
935
|
-
duration = time.time() - start_time
|
|
936
|
-
|
|
937
|
-
# Check if API key authentication is enabled
|
|
938
|
-
api_key_auth_enabled = self.auth_enabled and "api_key" in self.auth_methods
|
|
939
|
-
|
|
940
|
-
if api_key_auth_enabled:
|
|
941
|
-
# For configurations with API key auth, 401 is expected (success)
|
|
942
|
-
if response.status == 401:
|
|
943
|
-
return TestResult(
|
|
944
|
-
test_name=test_name,
|
|
945
|
-
server_url=self.base_url,
|
|
946
|
-
auth_type="api_key",
|
|
947
|
-
success=True,
|
|
948
|
-
status_code=response.status,
|
|
949
|
-
response_data={"expected": "authentication_failure"},
|
|
950
|
-
duration=duration,
|
|
951
|
-
)
|
|
952
|
-
else:
|
|
953
|
-
return TestResult(
|
|
954
|
-
test_name=test_name,
|
|
955
|
-
server_url=self.base_url,
|
|
956
|
-
auth_type="api_key",
|
|
957
|
-
success=False,
|
|
958
|
-
status_code=response.status,
|
|
959
|
-
error_message=f"Expected 401 Unauthorized, got {response.status}",
|
|
960
|
-
duration=duration,
|
|
961
|
-
)
|
|
962
|
-
else:
|
|
963
|
-
# For configurations without API key auth, 200 is expected (success)
|
|
964
|
-
if response.status == 200:
|
|
965
|
-
return TestResult(
|
|
966
|
-
test_name=test_name,
|
|
967
|
-
server_url=self.base_url,
|
|
968
|
-
auth_type="api_key",
|
|
969
|
-
success=True,
|
|
970
|
-
status_code=response.status,
|
|
971
|
-
response_data={"expected": "no_auth_required"},
|
|
972
|
-
duration=duration,
|
|
973
|
-
)
|
|
974
|
-
else:
|
|
975
|
-
return TestResult(
|
|
976
|
-
test_name=test_name,
|
|
977
|
-
server_url=self.base_url,
|
|
978
|
-
auth_type="api_key",
|
|
979
|
-
success=False,
|
|
980
|
-
status_code=response.status,
|
|
981
|
-
error_message=f"Expected 200 OK (no auth required), got {response.status}",
|
|
982
|
-
duration=duration,
|
|
983
|
-
)
|
|
984
|
-
except Exception as e:
|
|
985
|
-
duration = time.time() - start_time
|
|
986
|
-
return TestResult(
|
|
987
|
-
test_name=test_name,
|
|
988
|
-
server_url=self.base_url,
|
|
989
|
-
auth_type="api_key",
|
|
990
|
-
success=False,
|
|
991
|
-
error_message=f"Negative auth test error: {str(e)}",
|
|
992
|
-
duration=duration,
|
|
993
|
-
)
|
|
994
|
-
|
|
995
|
-
async def test_no_auth_required(self) -> TestResult:
|
|
996
|
-
"""Test that no authentication is required."""
|
|
997
|
-
return await self.test_echo_command(self.base_url, "none")
|
|
998
|
-
|
|
999
|
-
async def test_role_based_access(self, server_url: str, auth_type: str, role: str = "admin") -> TestResult:
|
|
1000
|
-
"""Test role-based access control."""
|
|
1001
|
-
if not self.roles_file and not self.roles:
|
|
1002
|
-
return TestResult(
|
|
1003
|
-
test_name="Role-Based Access Test",
|
|
1004
|
-
server_url=server_url,
|
|
1005
|
-
auth_type=auth_type,
|
|
1006
|
-
success=False,
|
|
1007
|
-
error_message="Role-based access error: role is required for role-based access test",
|
|
1008
|
-
)
|
|
1009
|
-
|
|
1010
|
-
# Use admin role for testing
|
|
1011
|
-
if auth_type == "api_key":
|
|
1012
|
-
api_key_value = self.api_keys.get("admin", "admin-secret-key")
|
|
1013
|
-
return await self.test_echo_command(server_url, auth_type, token=api_key_value, role=role)
|
|
1014
|
-
else:
|
|
1015
|
-
return await self.test_echo_command(server_url, auth_type, role=role)
|
|
1016
|
-
|
|
1017
|
-
async def test_role_permissions(self, server_url: str, auth_type: str, role: str = "admin", action: str = "read") -> TestResult:
|
|
1018
|
-
"""Test role permissions."""
|
|
1019
|
-
if not self.roles_file and not self.roles:
|
|
1020
|
-
return TestResult(
|
|
1021
|
-
test_name="Role Permissions Test",
|
|
1022
|
-
server_url=server_url,
|
|
1023
|
-
auth_type=auth_type,
|
|
1024
|
-
success=False,
|
|
1025
|
-
error_message="Role permissions test error: role is required for role permissions test",
|
|
1026
|
-
)
|
|
1027
|
-
|
|
1028
|
-
# Test with admin role
|
|
1029
|
-
if auth_type == "api_key":
|
|
1030
|
-
api_key_value = self.api_keys.get("admin", "admin-secret-key")
|
|
1031
|
-
return await self.test_echo_command(server_url, auth_type, token=api_key_value, role=role)
|
|
1032
|
-
else:
|
|
1033
|
-
return await self.test_echo_command(server_url, auth_type, role=role)
|
|
1034
|
-
|
|
1035
|
-
async def test_multiple_roles(self, server_url: str, auth_type: str) -> TestResult:
|
|
1036
|
-
"""Test multiple roles."""
|
|
1037
|
-
# Test with readonly role (should have read access)
|
|
1038
|
-
if auth_type == "api_key":
|
|
1039
|
-
api_key_value = self.api_keys.get("readonly", "readonly-token-123")
|
|
1040
|
-
result = await self.test_echo_command(server_url, auth_type, token=api_key_value, role="readonly")
|
|
1041
|
-
if result.success:
|
|
1042
|
-
return TestResult(
|
|
1043
|
-
test_name="Multiple Roles Test",
|
|
1044
|
-
server_url=server_url,
|
|
1045
|
-
auth_type=auth_type,
|
|
1046
|
-
success=True,
|
|
1047
|
-
response_data={"message": "Readonly role correctly has read access"},
|
|
1048
|
-
)
|
|
1049
|
-
else:
|
|
1050
|
-
return TestResult(
|
|
1051
|
-
test_name="Multiple Roles Test",
|
|
1052
|
-
server_url=server_url,
|
|
1053
|
-
auth_type=auth_type,
|
|
1054
|
-
success=False,
|
|
1055
|
-
error_message="Readonly role incorrectly denied read access",
|
|
1056
|
-
)
|
|
1057
|
-
elif auth_type == "certificate":
|
|
1058
|
-
# For certificate auth, test with user certificate (should have read access)
|
|
1059
|
-
result = await self.test_echo_command(server_url, auth_type, role="user")
|
|
1060
|
-
if result.success:
|
|
1061
|
-
return TestResult(
|
|
1062
|
-
test_name="Multiple Roles Test",
|
|
1063
|
-
server_url=server_url,
|
|
1064
|
-
auth_type=auth_type,
|
|
1065
|
-
success=True,
|
|
1066
|
-
response_data={"message": "User certificate correctly has read access"},
|
|
1067
|
-
)
|
|
1068
|
-
else:
|
|
1069
|
-
return TestResult(
|
|
1070
|
-
test_name="Multiple Roles Test",
|
|
1071
|
-
server_url=server_url,
|
|
1072
|
-
auth_type=auth_type,
|
|
1073
|
-
success=False,
|
|
1074
|
-
error_message="User certificate incorrectly denied read access",
|
|
1075
|
-
)
|
|
1076
|
-
else:
|
|
1077
|
-
return TestResult(
|
|
1078
|
-
test_name="Multiple Roles Test",
|
|
1079
|
-
server_url=server_url,
|
|
1080
|
-
auth_type=auth_type,
|
|
1081
|
-
success=False,
|
|
1082
|
-
error_message="Multiple roles test not implemented for this auth type",
|
|
1083
|
-
)
|
|
43
|
+
# Import security test components
|
|
44
|
+
from .security_test import SecurityTestClient
|
|
1084
45
|
|
|
1085
46
|
|
|
1086
47
|
async def main():
|
|
1087
|
-
"""Main function."""
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
"
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
"
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
parser.add_argument("--ca-cert", help="CA certificate file")
|
|
1109
|
-
args = parser.parse_args()
|
|
1110
|
-
if args.all_scenarios:
|
|
1111
|
-
# Test all scenarios
|
|
1112
|
-
async with SecurityTestClient() as client:
|
|
1113
|
-
await client.test_all_scenarios()
|
|
1114
|
-
client.print_summary()
|
|
1115
|
-
else:
|
|
1116
|
-
# Test single server
|
|
1117
|
-
async with SecurityTestClient(args.server) as client:
|
|
1118
|
-
await client.run_security_tests(args.server, args.auth, token=args.token)
|
|
1119
|
-
client.print_summary()
|
|
48
|
+
"""Main function to run security tests."""
|
|
49
|
+
print("š Starting MCP Proxy Adapter Security Tests")
|
|
50
|
+
print("=" * 50)
|
|
51
|
+
|
|
52
|
+
# Define test servers
|
|
53
|
+
test_servers = [
|
|
54
|
+
"http://localhost:8080", # HTTP Basic
|
|
55
|
+
"http://localhost:8080", # HTTP + Token
|
|
56
|
+
"https://localhost:8443", # HTTPS Basic
|
|
57
|
+
"https://localhost:8443", # HTTPS + Token
|
|
58
|
+
"https://localhost:20006", # mTLS Basic
|
|
59
|
+
"https://localhost:20007", # mTLS + Token
|
|
60
|
+
"https://localhost:20008", # mTLS + Roles
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
# Run security tests
|
|
64
|
+
async with SecurityTestClient() as client:
|
|
65
|
+
results = await client.run_security_tests(test_servers)
|
|
66
|
+
client.print_summary()
|
|
67
|
+
|
|
68
|
+
print("\\nš Security tests completed!")
|
|
1120
69
|
|
|
1121
70
|
|
|
1122
71
|
if __name__ == "__main__":
|