mcp-proxy-adapter 6.4.48__py3-none-any.whl → 6.6.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/middleware/unified_security.py +8 -12
- mcp_proxy_adapter/config.py +76 -117
- mcp_proxy_adapter/core/protocol_manager.py +25 -42
- mcp_proxy_adapter/core/security_integration.py +60 -97
- mcp_proxy_adapter/core/server_adapter.py +4 -0
- mcp_proxy_adapter/examples/check_config.py +415 -0
- mcp_proxy_adapter/examples/config_builder.py +142 -428
- mcp_proxy_adapter/examples/config_builder_simple.py +271 -0
- mcp_proxy_adapter/examples/generate_config.py +343 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +186 -23
- mcp_proxy_adapter/examples/security_test_client.py +21 -7
- mcp_proxy_adapter/examples/test_chk_hostname_automated.py +214 -0
- mcp_proxy_adapter/examples/test_config_builder.py +40 -0
- mcp_proxy_adapter/main.py +54 -27
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.4.48.dist-info → mcp_proxy_adapter-6.6.1.dist-info}/METADATA +1 -1
- {mcp_proxy_adapter-6.4.48.dist-info → mcp_proxy_adapter-6.6.1.dist-info}/RECORD +20 -16
- {mcp_proxy_adapter-6.4.48.dist-info → mcp_proxy_adapter-6.6.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.4.48.dist-info → mcp_proxy_adapter-6.6.1.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.4.48.dist-info → mcp_proxy_adapter-6.6.1.dist-info}/top_level.txt +0 -0
@@ -27,9 +27,7 @@ class SecurityTestRunner:
|
|
27
27
|
|
28
28
|
def __init__(self):
|
29
29
|
self.project_root = Path(__file__).parent.parent.parent
|
30
|
-
self.configs_dir =
|
31
|
-
self.project_root / "mcp_proxy_adapter" / "examples" / "configs"
|
32
|
-
)
|
30
|
+
self.configs_dir = self.project_root / "configs"
|
33
31
|
self.server_processes = {}
|
34
32
|
self.test_results = []
|
35
33
|
|
@@ -137,23 +135,21 @@ class SecurityTestRunner:
|
|
137
135
|
]
|
138
136
|
try:
|
139
137
|
# Get remaining config for client setup
|
140
|
-
auth_enabled = (
|
141
|
-
|
142
|
-
|
143
|
-
auth_methods = config.get("security", {}).get("auth", {}).get("methods", [])
|
138
|
+
auth_enabled = config.get("security", {}).get("enabled", False)
|
139
|
+
# For new simplified structure, if security is enabled, we use token auth
|
140
|
+
auth_methods = ["api_key"] if auth_enabled else []
|
144
141
|
# Create test client with correct protocol
|
145
|
-
|
146
|
-
|
147
|
-
)
|
142
|
+
server_protocol = config.get("server", {}).get("protocol", "http")
|
143
|
+
protocol = "https" if server_protocol in ["https", "mtls"] else "http"
|
148
144
|
client = SecurityTestClient(base_url=f"{protocol}://localhost:{port}")
|
145
|
+
print(f"🔍 DEBUG: Created client with URL: {client.base_url}")
|
149
146
|
client.auth_enabled = auth_enabled
|
150
147
|
client.auth_methods = auth_methods
|
151
|
-
client.api_keys = (
|
152
|
-
|
153
|
-
)
|
154
|
-
client.roles_file = config.get("security", {}).get("permissions", {}).get("roles_file")
|
148
|
+
client.api_keys = config.get("security", {}).get("tokens", {})
|
149
|
+
client.roles_file = config.get("security", {}).get("roles_file")
|
150
|
+
client.roles = config.get("security", {}).get("roles", {})
|
155
151
|
# For mTLS, override SSL context creation and change working directory
|
156
|
-
if
|
152
|
+
if server_protocol == "mtls":
|
157
153
|
client.create_ssl_context = client.create_ssl_context_for_mtls
|
158
154
|
# Ensure mTLS uses certificate auth
|
159
155
|
client.auth_methods = ["certificate"]
|
@@ -214,10 +210,10 @@ class SecurityTestRunner:
|
|
214
210
|
result = await client.test_negative_authentication()
|
215
211
|
results.append(result)
|
216
212
|
except Exception as e:
|
217
|
-
|
213
|
+
results.append(
|
218
214
|
TestResult(
|
219
215
|
test_name=f"{config_name}_client_error",
|
220
|
-
server_url=f"
|
216
|
+
server_url=f"{protocol}://localhost:{port}",
|
221
217
|
auth_type="none",
|
222
218
|
success=False,
|
223
219
|
error_message=str(e),
|
@@ -228,17 +224,68 @@ class SecurityTestRunner:
|
|
228
224
|
self.stop_server(config_name, process)
|
229
225
|
return results
|
230
226
|
|
227
|
+
def create_variant_from_full_config(self, full_config_path: Path, protocol: str, auth: str, port: int) -> Path:
|
228
|
+
"""
|
229
|
+
Create a variant configuration from full config.
|
230
|
+
|
231
|
+
Args:
|
232
|
+
full_config_path: Path to the full configuration file
|
233
|
+
protocol: Protocol type (http, https, mtls)
|
234
|
+
auth: Authentication type (none, token, token_roles)
|
235
|
+
port: Server port
|
236
|
+
|
237
|
+
Returns:
|
238
|
+
Path to the temporary configuration file
|
239
|
+
"""
|
240
|
+
import tempfile
|
241
|
+
import json
|
242
|
+
|
243
|
+
# Load the full configuration
|
244
|
+
with open(full_config_path, 'r') as f:
|
245
|
+
full_config = json.load(f)
|
246
|
+
|
247
|
+
# Create a copy of the full config
|
248
|
+
variant_config = full_config.copy()
|
249
|
+
|
250
|
+
# Set server port and protocol
|
251
|
+
variant_config["server"]["port"] = port
|
252
|
+
variant_config["server"]["protocol"] = protocol
|
253
|
+
|
254
|
+
# Apply protocol configuration
|
255
|
+
if protocol in variant_config.get("protocol_variants", {}):
|
256
|
+
protocol_config = variant_config["protocol_variants"][protocol]
|
257
|
+
variant_config["server"].update(protocol_config["server"])
|
258
|
+
|
259
|
+
# Apply authentication configuration
|
260
|
+
if auth in variant_config.get("auth_variants", {}):
|
261
|
+
auth_config = variant_config["auth_variants"][auth]
|
262
|
+
variant_config["security"].update(auth_config["security"])
|
263
|
+
|
264
|
+
# Remove the helper sections
|
265
|
+
variant_config.pop("protocol_variants", None)
|
266
|
+
variant_config.pop("auth_variants", None)
|
267
|
+
|
268
|
+
# Create temporary config file
|
269
|
+
temp_dir = tempfile.mkdtemp(prefix="full_config_test_")
|
270
|
+
config_name = f"{protocol}_{auth}.json"
|
271
|
+
config_path = Path(temp_dir) / config_name
|
272
|
+
|
273
|
+
with open(config_path, 'w') as f:
|
274
|
+
json.dump(variant_config, f, indent=2, ensure_ascii=False)
|
275
|
+
|
276
|
+
return config_path
|
277
|
+
|
231
278
|
async def run_all_tests(self):
|
232
279
|
"""Run all security tests."""
|
233
280
|
print("🔒 Starting Security Testing Suite")
|
234
281
|
print("=" * 50)
|
235
282
|
# Test configurations
|
236
283
|
configs = [
|
237
|
-
("basic_http", "
|
238
|
-
("http_token", "
|
239
|
-
("https", "
|
240
|
-
("https_token", "
|
241
|
-
("mtls", "
|
284
|
+
("basic_http", "http.json"),
|
285
|
+
("http_token", "http_token_roles.json"),
|
286
|
+
("https", "https.json"),
|
287
|
+
("https_token", "https_token_roles.json"),
|
288
|
+
("mtls", "mtls.json"),
|
242
289
|
]
|
243
290
|
total_tests = 0
|
244
291
|
passed_tests = 0
|
@@ -280,12 +327,128 @@ class SecurityTestRunner:
|
|
280
327
|
print(f" Error: {result.error_message}")
|
281
328
|
return passed_tests == total_tests
|
282
329
|
|
330
|
+
async def run_full_config_tests(self, full_config_path: str):
|
331
|
+
"""Run tests using full configuration with all variants."""
|
332
|
+
print("🚀 Full Configuration Variants Testing")
|
333
|
+
print("=" * 60)
|
334
|
+
print(f"📁 Using full config: {full_config_path}")
|
335
|
+
|
336
|
+
full_config_file = Path(full_config_path)
|
337
|
+
if not full_config_file.exists():
|
338
|
+
print(f"❌ Full configuration file not found: {full_config_path}")
|
339
|
+
return False
|
340
|
+
|
341
|
+
# Define all combinations to test
|
342
|
+
variants = [
|
343
|
+
# HTTP variants
|
344
|
+
("http", "none", 20000),
|
345
|
+
("http", "token", 20001),
|
346
|
+
("http", "token_roles", 20002),
|
347
|
+
|
348
|
+
# HTTPS variants
|
349
|
+
("https", "none", 20003),
|
350
|
+
("https", "token", 20004),
|
351
|
+
("https", "token_roles", 20005),
|
352
|
+
|
353
|
+
# mTLS variants
|
354
|
+
("mtls", "none", 20006),
|
355
|
+
("mtls", "token", 20007),
|
356
|
+
("mtls", "token_roles", 20008),
|
357
|
+
]
|
358
|
+
|
359
|
+
total_tests = 0
|
360
|
+
passed_tests = 0
|
361
|
+
all_results = []
|
362
|
+
|
363
|
+
for protocol, auth, port in variants:
|
364
|
+
print(f"\n{'='*60}")
|
365
|
+
print(f"🧪 Testing {protocol.upper()} with {auth.upper()} authentication")
|
366
|
+
print(f"{'='*60}")
|
367
|
+
|
368
|
+
# Create variant configuration
|
369
|
+
config_path = self.create_variant_from_full_config(full_config_file, protocol, auth, port)
|
370
|
+
|
371
|
+
# Test the variant
|
372
|
+
config_name = f"{protocol}_{auth}"
|
373
|
+
results = await self.test_server(config_name, config_path)
|
374
|
+
|
375
|
+
# Count results
|
376
|
+
for result in results:
|
377
|
+
total_tests += 1
|
378
|
+
if result.success:
|
379
|
+
passed_tests += 1
|
380
|
+
print(f"✅ {result.test_name}: PASS")
|
381
|
+
else:
|
382
|
+
print(f"❌ {result.test_name}: FAIL - {result.error_message}")
|
383
|
+
|
384
|
+
all_results.extend(results)
|
385
|
+
|
386
|
+
# Clean up temporary config
|
387
|
+
import shutil
|
388
|
+
shutil.rmtree(config_path.parent)
|
389
|
+
|
390
|
+
# Print final summary
|
391
|
+
print(f"\n{'='*60}")
|
392
|
+
print("📊 FULL CONFIG TEST SUMMARY")
|
393
|
+
print(f"{'='*60}")
|
394
|
+
print(f"Total tests: {total_tests}")
|
395
|
+
print(f"Passed: {passed_tests}")
|
396
|
+
print(f"Failed: {total_tests - passed_tests}")
|
397
|
+
print(f"Success rate: {(passed_tests/total_tests)*100:.1f}%")
|
398
|
+
|
399
|
+
if total_tests - passed_tests > 0:
|
400
|
+
print(f"\n❌ Failed tests:")
|
401
|
+
for result in all_results:
|
402
|
+
if not result.success:
|
403
|
+
print(f" • {result.test_name}: {result.error_message}")
|
404
|
+
|
405
|
+
return passed_tests == total_tests
|
406
|
+
|
407
|
+
def print_summary(self):
|
408
|
+
"""Print test summary."""
|
409
|
+
if not self.test_results:
|
410
|
+
print("📊 No test results to display")
|
411
|
+
return
|
412
|
+
|
413
|
+
total_tests = len(self.test_results)
|
414
|
+
passed_tests = sum(1 for result in self.test_results if result.success)
|
415
|
+
|
416
|
+
print("\n" + "=" * 50)
|
417
|
+
print("📊 TEST SUMMARY")
|
418
|
+
print("=" * 50)
|
419
|
+
print(f"Total tests: {total_tests}")
|
420
|
+
print(f"Passed: {passed_tests}")
|
421
|
+
print(f"Failed: {total_tests - passed_tests}")
|
422
|
+
print(f"Success rate: {(passed_tests/total_tests)*100:.1f}%")
|
423
|
+
|
424
|
+
if total_tests - passed_tests > 0:
|
425
|
+
print(f"\n📋 DETAILED RESULTS")
|
426
|
+
print("-" * 30)
|
427
|
+
for result in self.test_results:
|
428
|
+
status = "✅ PASS" if result.success else "❌ FAIL"
|
429
|
+
print(f"{status} {result.test_name}")
|
430
|
+
if not result.success and result.error_message:
|
431
|
+
print(f" Error: {result.error_message}")
|
432
|
+
|
283
433
|
|
284
434
|
async def main():
|
285
435
|
"""Main function."""
|
436
|
+
import argparse
|
437
|
+
|
438
|
+
parser = argparse.ArgumentParser(description="Security Testing Suite for MCP Proxy Adapter")
|
439
|
+
parser.add_argument("--full-config", help="Path to full configuration file for variant testing")
|
440
|
+
parser.add_argument("--verbose", action="store_true", help="Enable verbose output")
|
441
|
+
|
442
|
+
args = parser.parse_args()
|
443
|
+
|
286
444
|
runner = SecurityTestRunner()
|
287
445
|
try:
|
288
|
-
|
446
|
+
if args.full_config:
|
447
|
+
# Test full configuration variants
|
448
|
+
success = await runner.run_full_config_tests(args.full_config)
|
449
|
+
else:
|
450
|
+
# Run standard tests
|
451
|
+
success = await runner.run_all_tests()
|
289
452
|
sys.exit(0 if success else 1)
|
290
453
|
except KeyboardInterrupt:
|
291
454
|
print("\n⚠️ Testing interrupted by user")
|
@@ -83,6 +83,7 @@ class SecurityTestClient:
|
|
83
83
|
self.auth_methods = []
|
84
84
|
self.api_keys = {}
|
85
85
|
self.roles_file = None
|
86
|
+
self.roles = {}
|
86
87
|
|
87
88
|
if self._security_available:
|
88
89
|
# For testing purposes, we don't initialize SecurityManager
|
@@ -99,7 +100,7 @@ class SecurityTestClient:
|
|
99
100
|
self.test_tokens = {
|
100
101
|
"admin": "admin-secret-key",
|
101
102
|
"user": "user-secret-key",
|
102
|
-
"readonly": "readonly-
|
103
|
+
"readonly": "readonly-secret-key",
|
103
104
|
"guest": "guest-token-123",
|
104
105
|
"proxy": "proxy-token-123",
|
105
106
|
"invalid": "invalid-token-999",
|
@@ -123,9 +124,11 @@ class SecurityTestClient:
|
|
123
124
|
def create_ssl_context_for_mtls(self) -> ssl.SSLContext:
|
124
125
|
"""Create SSL context for mTLS connections."""
|
125
126
|
# For mTLS, we need client certificates - check if they exist
|
126
|
-
|
127
|
-
|
128
|
-
|
127
|
+
# Paths are relative to project root (../../ from examples directory)
|
128
|
+
# Use admin certificates to match server configuration
|
129
|
+
cert_file = "../../certs/admin.crt"
|
130
|
+
key_file = "../../certs/client_admin.key"
|
131
|
+
ca_cert_file = "../../certs/localhost_server.crt"
|
129
132
|
|
130
133
|
# CRITICAL: For mTLS, certificates are REQUIRED
|
131
134
|
if not os.path.exists(cert_file):
|
@@ -152,7 +155,13 @@ class SecurityTestClient:
|
|
152
155
|
timeout = ClientTimeout(total=30)
|
153
156
|
# Create SSL context only for HTTPS URLs
|
154
157
|
if self.base_url.startswith('https://'):
|
155
|
-
|
158
|
+
# Check if this is mTLS (ports 20006, 20007, 20008 are mTLS test ports)
|
159
|
+
if any(port in self.base_url for port in ['20006', '20007', '20008']):
|
160
|
+
# Use mTLS context with client certificates
|
161
|
+
ssl_context = self.create_ssl_context_for_mtls()
|
162
|
+
else:
|
163
|
+
# Use regular HTTPS context
|
164
|
+
ssl_context = self.create_ssl_context()
|
156
165
|
connector = TCPConnector(ssl=ssl_context)
|
157
166
|
else:
|
158
167
|
# For HTTP URLs, use default connector without SSL
|
@@ -209,6 +218,11 @@ class SecurityTestClient:
|
|
209
218
|
# Provide both common header styles to maximize compatibility
|
210
219
|
headers["X-API-Key"] = token
|
211
220
|
headers["Authorization"] = f"Bearer {token}"
|
221
|
+
|
222
|
+
# Add role header if provided
|
223
|
+
role = kwargs.get("role")
|
224
|
+
if role:
|
225
|
+
headers["X-Role"] = role
|
212
226
|
elif auth_type == "basic":
|
213
227
|
username = kwargs.get("username")
|
214
228
|
password = kwargs.get("password")
|
@@ -983,7 +997,7 @@ class SecurityTestClient:
|
|
983
997
|
|
984
998
|
async def test_role_based_access(self, server_url: str, auth_type: str, role: str = "admin") -> TestResult:
|
985
999
|
"""Test role-based access control."""
|
986
|
-
if not self.roles_file:
|
1000
|
+
if not self.roles_file and not self.roles:
|
987
1001
|
return TestResult(
|
988
1002
|
test_name="Role-Based Access Test",
|
989
1003
|
server_url=server_url,
|
@@ -1001,7 +1015,7 @@ class SecurityTestClient:
|
|
1001
1015
|
|
1002
1016
|
async def test_role_permissions(self, server_url: str, auth_type: str, role: str = "admin", action: str = "read") -> TestResult:
|
1003
1017
|
"""Test role permissions."""
|
1004
|
-
if not self.roles_file:
|
1018
|
+
if not self.roles_file and not self.roles:
|
1005
1019
|
return TestResult(
|
1006
1020
|
test_name="Role Permissions Test",
|
1007
1021
|
server_url=server_url,
|
@@ -0,0 +1,214 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Automated tests for chk_hostname functionality in all SSL modes.
|
4
|
+
|
5
|
+
Author: Vasiliy Zdanovskiy
|
6
|
+
email: vasilyvz@gmail.com
|
7
|
+
"""
|
8
|
+
|
9
|
+
import json
|
10
|
+
import tempfile
|
11
|
+
import os
|
12
|
+
from pathlib import Path
|
13
|
+
import sys
|
14
|
+
|
15
|
+
# Add the project root to the path
|
16
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
17
|
+
|
18
|
+
from mcp_proxy_adapter.config import Config
|
19
|
+
from mcp_proxy_adapter.examples.config_builder import ConfigBuilder, Protocol, AuthMethod
|
20
|
+
|
21
|
+
|
22
|
+
def test_chk_hostname_default_config():
|
23
|
+
"""Test that default config has chk_hostname=False for HTTP."""
|
24
|
+
print("🧪 Testing default config chk_hostname...")
|
25
|
+
|
26
|
+
config = Config()
|
27
|
+
|
28
|
+
# Default should be HTTP with chk_hostname=False
|
29
|
+
assert config.get("server.protocol") == "http"
|
30
|
+
assert config.get("transport.ssl.chk_hostname") is False
|
31
|
+
|
32
|
+
print("✅ Default config: chk_hostname=False for HTTP")
|
33
|
+
|
34
|
+
|
35
|
+
def test_chk_hostname_http_config():
|
36
|
+
"""Test that HTTP config has chk_hostname=False."""
|
37
|
+
print("🧪 Testing HTTP config chk_hostname...")
|
38
|
+
|
39
|
+
# Create HTTP config
|
40
|
+
http_config = ConfigBuilder().set_protocol(Protocol.HTTP).build()
|
41
|
+
|
42
|
+
# Save to temporary file and load with Config
|
43
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
44
|
+
json.dump(http_config, f)
|
45
|
+
temp_config_path = f.name
|
46
|
+
|
47
|
+
try:
|
48
|
+
config = Config(temp_config_path)
|
49
|
+
|
50
|
+
# HTTP should have chk_hostname=False
|
51
|
+
assert config.get("server.protocol") == "http"
|
52
|
+
assert config.get("transport.ssl.chk_hostname") is False
|
53
|
+
|
54
|
+
print("✅ HTTP config: chk_hostname=False")
|
55
|
+
finally:
|
56
|
+
os.unlink(temp_config_path)
|
57
|
+
|
58
|
+
|
59
|
+
def test_chk_hostname_https_config():
|
60
|
+
"""Test that HTTPS config has chk_hostname=True."""
|
61
|
+
print("🧪 Testing HTTPS config chk_hostname...")
|
62
|
+
|
63
|
+
# Create HTTPS config
|
64
|
+
https_config = ConfigBuilder().set_protocol(Protocol.HTTPS).build()
|
65
|
+
|
66
|
+
# Save to temporary file and load with Config
|
67
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
68
|
+
json.dump(https_config, f)
|
69
|
+
temp_config_path = f.name
|
70
|
+
|
71
|
+
try:
|
72
|
+
config = Config(temp_config_path)
|
73
|
+
|
74
|
+
|
75
|
+
# HTTPS should have chk_hostname=True
|
76
|
+
assert config.get("server.protocol") == "https"
|
77
|
+
assert config.get("transport.ssl.chk_hostname") is True
|
78
|
+
|
79
|
+
print("✅ HTTPS config: chk_hostname=True")
|
80
|
+
finally:
|
81
|
+
os.unlink(temp_config_path)
|
82
|
+
|
83
|
+
|
84
|
+
def test_chk_hostname_mtls_config():
|
85
|
+
"""Test that mTLS config has chk_hostname=True."""
|
86
|
+
print("🧪 Testing mTLS config chk_hostname...")
|
87
|
+
|
88
|
+
# Create mTLS config
|
89
|
+
mtls_config = ConfigBuilder().set_protocol(Protocol.MTLS).build()
|
90
|
+
|
91
|
+
# Save to temporary file and load with Config
|
92
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
93
|
+
json.dump(mtls_config, f)
|
94
|
+
temp_config_path = f.name
|
95
|
+
|
96
|
+
try:
|
97
|
+
config = Config(temp_config_path)
|
98
|
+
|
99
|
+
# mTLS should have chk_hostname=True
|
100
|
+
assert config.get("server.protocol") == "mtls"
|
101
|
+
assert config.get("transport.ssl.chk_hostname") is True
|
102
|
+
|
103
|
+
print("✅ mTLS config: chk_hostname=True")
|
104
|
+
finally:
|
105
|
+
os.unlink(temp_config_path)
|
106
|
+
|
107
|
+
|
108
|
+
def test_chk_hostname_override():
|
109
|
+
"""Test that chk_hostname can be overridden in config."""
|
110
|
+
print("🧪 Testing chk_hostname override...")
|
111
|
+
|
112
|
+
# Create HTTPS config with chk_hostname=False override
|
113
|
+
https_config = ConfigBuilder().set_protocol(Protocol.HTTPS).build()
|
114
|
+
# Add transport section if it doesn't exist
|
115
|
+
if "transport" not in https_config:
|
116
|
+
https_config["transport"] = {}
|
117
|
+
if "ssl" not in https_config["transport"]:
|
118
|
+
https_config["transport"]["ssl"] = {}
|
119
|
+
https_config["transport"]["ssl"]["chk_hostname"] = False
|
120
|
+
https_config["transport"]["ssl"]["_chk_hostname_user_set"] = True
|
121
|
+
|
122
|
+
# Save to temporary file and load with Config
|
123
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
124
|
+
json.dump(https_config, f)
|
125
|
+
temp_config_path = f.name
|
126
|
+
|
127
|
+
try:
|
128
|
+
config = Config(temp_config_path)
|
129
|
+
|
130
|
+
|
131
|
+
# Should respect the override
|
132
|
+
assert config.get("server.protocol") == "https"
|
133
|
+
assert config.get("transport.ssl.chk_hostname") is False
|
134
|
+
|
135
|
+
print("✅ HTTPS config with chk_hostname=False override works")
|
136
|
+
finally:
|
137
|
+
os.unlink(temp_config_path)
|
138
|
+
|
139
|
+
|
140
|
+
def test_chk_hostname_all_combinations():
|
141
|
+
"""Test chk_hostname for all protocol and auth combinations."""
|
142
|
+
print("🧪 Testing chk_hostname for all combinations...")
|
143
|
+
|
144
|
+
protocols = [Protocol.HTTP, Protocol.HTTPS, Protocol.MTLS]
|
145
|
+
auth_methods = [AuthMethod.NONE, AuthMethod.TOKEN, AuthMethod.TOKEN_ROLES]
|
146
|
+
|
147
|
+
for protocol in protocols:
|
148
|
+
for auth_method in auth_methods:
|
149
|
+
# Create config
|
150
|
+
config_data = (ConfigBuilder()
|
151
|
+
.set_protocol(protocol)
|
152
|
+
.set_auth(auth_method)
|
153
|
+
.build())
|
154
|
+
|
155
|
+
# Save to temporary file and load with Config
|
156
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
157
|
+
json.dump(config_data, f)
|
158
|
+
temp_config_path = f.name
|
159
|
+
|
160
|
+
try:
|
161
|
+
config = Config(temp_config_path)
|
162
|
+
|
163
|
+
protocol_name = protocol.value
|
164
|
+
auth_name = auth_method.value
|
165
|
+
|
166
|
+
# Check chk_hostname based on protocol
|
167
|
+
if protocol_name == "http":
|
168
|
+
expected_chk_hostname = False
|
169
|
+
else: # https or mtls
|
170
|
+
expected_chk_hostname = True
|
171
|
+
|
172
|
+
actual_chk_hostname = config.get("transport.ssl.chk_hostname")
|
173
|
+
|
174
|
+
assert actual_chk_hostname == expected_chk_hostname, (
|
175
|
+
f"Protocol {protocol_name} with auth {auth_name}: "
|
176
|
+
f"expected chk_hostname={expected_chk_hostname}, "
|
177
|
+
f"got {actual_chk_hostname}"
|
178
|
+
)
|
179
|
+
|
180
|
+
print(f"✅ {protocol_name}+{auth_name}: chk_hostname={actual_chk_hostname}")
|
181
|
+
|
182
|
+
finally:
|
183
|
+
os.unlink(temp_config_path)
|
184
|
+
|
185
|
+
print("✅ All protocol+auth combinations have correct chk_hostname values")
|
186
|
+
|
187
|
+
|
188
|
+
def main():
|
189
|
+
"""Run all chk_hostname tests."""
|
190
|
+
print("🧪 Running Automated chk_hostname Tests")
|
191
|
+
print("=" * 50)
|
192
|
+
|
193
|
+
try:
|
194
|
+
test_chk_hostname_default_config()
|
195
|
+
test_chk_hostname_http_config()
|
196
|
+
test_chk_hostname_https_config()
|
197
|
+
test_chk_hostname_mtls_config()
|
198
|
+
test_chk_hostname_override()
|
199
|
+
test_chk_hostname_all_combinations()
|
200
|
+
|
201
|
+
print("=" * 50)
|
202
|
+
print("🎉 All chk_hostname tests passed!")
|
203
|
+
return True
|
204
|
+
|
205
|
+
except Exception as e:
|
206
|
+
print(f"❌ Test failed: {e}")
|
207
|
+
import traceback
|
208
|
+
traceback.print_exc()
|
209
|
+
return False
|
210
|
+
|
211
|
+
|
212
|
+
if __name__ == "__main__":
|
213
|
+
success = main()
|
214
|
+
sys.exit(0 if success else 1)
|
@@ -60,7 +60,9 @@ class TestConfigBuilder:
|
|
60
60
|
config = builder.set_protocol(Protocol.HTTP).build()
|
61
61
|
|
62
62
|
assert config["ssl"]["enabled"] is False
|
63
|
+
assert config["ssl"]["chk_hostname"] is False
|
63
64
|
assert config["security"]["ssl"]["enabled"] is False
|
65
|
+
assert config["security"]["ssl"]["chk_hostname"] is False
|
64
66
|
assert config["protocols"]["allowed_protocols"] == ["http"]
|
65
67
|
assert config["protocols"]["default_protocol"] == "http"
|
66
68
|
assert config["protocols"]["protocol_handlers"]["http"]["enabled"] is True
|
@@ -73,11 +75,13 @@ class TestConfigBuilder:
|
|
73
75
|
config = builder.set_protocol(Protocol.HTTPS, cert_dir="/tmp/certs", key_dir="/tmp/keys").build()
|
74
76
|
|
75
77
|
assert config["ssl"]["enabled"] is True
|
78
|
+
assert config["ssl"]["chk_hostname"] is True
|
76
79
|
assert config["ssl"]["cert_file"] == "/tmp/certs/server_cert.pem"
|
77
80
|
assert config["ssl"]["key_file"] == "/tmp/keys/server_key.pem"
|
78
81
|
assert config["ssl"]["ca_cert"] == "/tmp/certs/ca_cert.pem"
|
79
82
|
|
80
83
|
assert config["security"]["ssl"]["enabled"] is True
|
84
|
+
assert config["security"]["ssl"]["chk_hostname"] is True
|
81
85
|
assert config["security"]["ssl"]["cert_file"] == "/tmp/certs/server_cert.pem"
|
82
86
|
assert config["security"]["ssl"]["key_file"] == "/tmp/keys/server_key.pem"
|
83
87
|
assert config["security"]["ssl"]["ca_cert_file"] == "/tmp/certs/ca_cert.pem"
|
@@ -94,10 +98,12 @@ class TestConfigBuilder:
|
|
94
98
|
config = builder.set_protocol(Protocol.MTLS, cert_dir="/tmp/certs", key_dir="/tmp/keys").build()
|
95
99
|
|
96
100
|
assert config["ssl"]["enabled"] is True
|
101
|
+
assert config["ssl"]["chk_hostname"] is True
|
97
102
|
assert config["ssl"]["verify_client"] is True
|
98
103
|
assert config["ssl"]["client_cert_required"] is True
|
99
104
|
|
100
105
|
assert config["security"]["ssl"]["enabled"] is True
|
106
|
+
assert config["security"]["ssl"]["chk_hostname"] is True
|
101
107
|
assert config["security"]["ssl"]["client_cert_file"] == "/tmp/certs/admin_cert.pem"
|
102
108
|
assert config["security"]["ssl"]["client_key_file"] == "/tmp/keys/admin_key.pem"
|
103
109
|
assert config["security"]["ssl"]["verify_mode"] == "CERT_REQUIRED"
|
@@ -186,6 +192,40 @@ class TestConfigBuilder:
|
|
186
192
|
assert config["commands"]["enabled_commands"] == enabled
|
187
193
|
assert config["commands"]["disabled_commands"] == disabled
|
188
194
|
|
195
|
+
def test_set_hostname_check(self):
|
196
|
+
"""Test hostname check configuration."""
|
197
|
+
builder = ConfigBuilder()
|
198
|
+
|
199
|
+
# Test enabling hostname check
|
200
|
+
config = builder.set_hostname_check(enabled=True).build()
|
201
|
+
assert config["ssl"]["chk_hostname"] is True
|
202
|
+
assert config["security"]["ssl"]["chk_hostname"] is True
|
203
|
+
|
204
|
+
# Test disabling hostname check
|
205
|
+
config = builder.set_hostname_check(enabled=False).build()
|
206
|
+
assert config["ssl"]["chk_hostname"] is False
|
207
|
+
assert config["security"]["ssl"]["chk_hostname"] is False
|
208
|
+
|
209
|
+
def test_hostname_check_with_protocols(self):
|
210
|
+
"""Test hostname check behavior with different protocols."""
|
211
|
+
# HTTP should have chk_hostname = False
|
212
|
+
builder = ConfigBuilder()
|
213
|
+
config = builder.set_protocol(Protocol.HTTP).build()
|
214
|
+
assert config["ssl"]["chk_hostname"] is False
|
215
|
+
assert config["security"]["ssl"]["chk_hostname"] is False
|
216
|
+
|
217
|
+
# HTTPS should have chk_hostname = True
|
218
|
+
builder = ConfigBuilder()
|
219
|
+
config = builder.set_protocol(Protocol.HTTPS).build()
|
220
|
+
assert config["ssl"]["chk_hostname"] is True
|
221
|
+
assert config["security"]["ssl"]["chk_hostname"] is True
|
222
|
+
|
223
|
+
# mTLS should have chk_hostname = True
|
224
|
+
builder = ConfigBuilder()
|
225
|
+
config = builder.set_protocol(Protocol.MTLS).build()
|
226
|
+
assert config["ssl"]["chk_hostname"] is True
|
227
|
+
assert config["security"]["ssl"]["chk_hostname"] is True
|
228
|
+
|
189
229
|
def test_save_configuration(self):
|
190
230
|
"""Test saving configuration to file."""
|
191
231
|
with tempfile.TemporaryDirectory() as temp_dir:
|