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.
@@ -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
- config.get("security", {}).get("auth", {}).get("enabled", False)
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
- protocol = (
146
- "https" if config.get("ssl", {}).get("enabled", False) else "http"
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
- config.get("security", {}).get("auth", {}).get("api_keys", {})
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 config_name == "mtls":
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
- results.append(
213
+ results.append(
218
214
  TestResult(
219
215
  test_name=f"{config_name}_client_error",
220
- server_url=f"http://localhost:{port}",
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", "http_simple.json"),
238
- ("http_token", "http_token.json"),
239
- ("https", "https_simple.json"),
240
- ("https_token", "https_token.json"),
241
- ("mtls", "mtls_simple.json"),
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
- success = await runner.run_all_tests()
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-token-123",
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
- cert_file = "./certs/user_cert.pem"
127
- key_file = "./keys/user_key.pem"
128
- ca_cert_file = "./certs/mcp_proxy_adapter_ca_ca.crt"
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
- ssl_context = self.create_ssl_context()
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: