mcp-proxy-adapter 6.8.2__py3-none-any.whl → 6.9.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/config.py +208 -110
- mcp_proxy_adapter/core/config_validator.py +1077 -189
- mcp_proxy_adapter/core/errors.py +42 -0
- mcp_proxy_adapter/core/logging.py +16 -16
- mcp_proxy_adapter/examples/config_builder.py +192 -741
- mcp_proxy_adapter/examples/full_application/main.py +10 -2
- mcp_proxy_adapter/examples/full_application/run_mtls.py +252 -0
- mcp_proxy_adapter/examples/full_application/run_simple.py +152 -0
- mcp_proxy_adapter/examples/full_application/test_server.py +221 -0
- mcp_proxy_adapter/examples/generate_config.py +61 -8
- mcp_proxy_adapter/examples/test_config.py +47 -2
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.8.2.dist-info → mcp_proxy_adapter-6.9.1.dist-info}/METADATA +1 -1
- {mcp_proxy_adapter-6.8.2.dist-info → mcp_proxy_adapter-6.9.1.dist-info}/RECORD +17 -14
- {mcp_proxy_adapter-6.8.2.dist-info → mcp_proxy_adapter-6.9.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.8.2.dist-info → mcp_proxy_adapter-6.9.1.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.8.2.dist-info → mcp_proxy_adapter-6.9.1.dist-info}/top_level.txt +0 -0
@@ -28,12 +28,20 @@ class FullApplication:
|
|
28
28
|
|
29
29
|
def __init__(self, config_path: str):
|
30
30
|
self.config_path = config_path
|
31
|
-
|
31
|
+
try:
|
32
|
+
self.config = Config(config_path, validate_on_load=True)
|
33
|
+
self.logger = logging.getLogger(__name__)
|
34
|
+
self.logger.info("✅ Configuration loaded and validated successfully")
|
35
|
+
except Exception as e:
|
36
|
+
logging.basicConfig(level=logging.ERROR)
|
37
|
+
self.logger = logging.getLogger(__name__)
|
38
|
+
self.logger.error(f"❌ Configuration error: {e}")
|
39
|
+
raise
|
40
|
+
|
32
41
|
self.app = None
|
33
42
|
self.command_registry = None
|
34
43
|
# Setup logging
|
35
44
|
logging.basicConfig(level=logging.INFO)
|
36
|
-
self.logger = logging.getLogger(__name__)
|
37
45
|
|
38
46
|
def setup_hooks(self):
|
39
47
|
"""Setup application hooks."""
|
@@ -0,0 +1,252 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
mTLS Full Application Runner
|
4
|
+
Runs the full application example with mTLS configuration.
|
5
|
+
|
6
|
+
Author: Vasiliy Zdanovskiy
|
7
|
+
email: vasilyvz@gmail.com
|
8
|
+
"""
|
9
|
+
|
10
|
+
import sys
|
11
|
+
import argparse
|
12
|
+
import logging
|
13
|
+
import json
|
14
|
+
import ssl
|
15
|
+
import socket
|
16
|
+
from pathlib import Path
|
17
|
+
|
18
|
+
# Add the framework to the path
|
19
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
20
|
+
|
21
|
+
def validate_mtls_config(config_path: str) -> bool:
|
22
|
+
"""Validate mTLS configuration file."""
|
23
|
+
try:
|
24
|
+
with open(config_path, 'r') as f:
|
25
|
+
config = json.load(f)
|
26
|
+
|
27
|
+
# Check required sections
|
28
|
+
required_sections = [
|
29
|
+
'uuid', 'server', 'logging', 'commands', 'transport',
|
30
|
+
'proxy_registration', 'debug', 'security', 'roles', 'ssl'
|
31
|
+
]
|
32
|
+
|
33
|
+
missing_sections = []
|
34
|
+
for section in required_sections:
|
35
|
+
if section not in config:
|
36
|
+
missing_sections.append(section)
|
37
|
+
|
38
|
+
if missing_sections:
|
39
|
+
print(f"❌ Missing required sections: {missing_sections}")
|
40
|
+
return False
|
41
|
+
|
42
|
+
# Check SSL configuration
|
43
|
+
ssl_config = config.get('ssl', {})
|
44
|
+
if not ssl_config.get('enabled', False):
|
45
|
+
print("❌ SSL must be enabled for mTLS")
|
46
|
+
return False
|
47
|
+
|
48
|
+
# Check certificate files
|
49
|
+
cert_file = ssl_config.get('cert_file')
|
50
|
+
key_file = ssl_config.get('key_file')
|
51
|
+
ca_cert_file = ssl_config.get('ca_cert_file')
|
52
|
+
|
53
|
+
if not cert_file or not key_file or not ca_cert_file:
|
54
|
+
print("❌ SSL configuration missing certificate files")
|
55
|
+
return False
|
56
|
+
|
57
|
+
# Check if certificate files exist
|
58
|
+
cert_path = Path(cert_file)
|
59
|
+
key_path = Path(key_file)
|
60
|
+
ca_path = Path(ca_cert_file)
|
61
|
+
|
62
|
+
if not cert_path.exists():
|
63
|
+
print(f"❌ Certificate file not found: {cert_file}")
|
64
|
+
return False
|
65
|
+
|
66
|
+
if not key_path.exists():
|
67
|
+
print(f"❌ Key file not found: {key_file}")
|
68
|
+
return False
|
69
|
+
|
70
|
+
if not ca_path.exists():
|
71
|
+
print(f"❌ CA certificate file not found: {ca_cert_file}")
|
72
|
+
return False
|
73
|
+
|
74
|
+
print("✅ mTLS Configuration validation passed")
|
75
|
+
return True
|
76
|
+
|
77
|
+
except FileNotFoundError:
|
78
|
+
print(f"❌ Configuration file not found: {config_path}")
|
79
|
+
return False
|
80
|
+
except json.JSONDecodeError as e:
|
81
|
+
print(f"❌ Invalid JSON in configuration: {e}")
|
82
|
+
return False
|
83
|
+
except Exception as e:
|
84
|
+
print(f"❌ Configuration validation error: {e}")
|
85
|
+
return False
|
86
|
+
|
87
|
+
def test_mtls_connection(config_path: str):
|
88
|
+
"""Test mTLS connection to the server."""
|
89
|
+
try:
|
90
|
+
with open(config_path, 'r') as f:
|
91
|
+
config = json.load(f)
|
92
|
+
|
93
|
+
server_config = config.get('server', {})
|
94
|
+
host = server_config.get('host', '0.0.0.0')
|
95
|
+
port = server_config.get('port', 8443)
|
96
|
+
|
97
|
+
ssl_config = config.get('ssl', {})
|
98
|
+
cert_file = ssl_config.get('cert_file')
|
99
|
+
key_file = ssl_config.get('key_file')
|
100
|
+
ca_cert_file = ssl_config.get('ca_cert_file')
|
101
|
+
|
102
|
+
print(f"🔐 Testing mTLS connection to {host}:{port}")
|
103
|
+
|
104
|
+
# Create SSL context
|
105
|
+
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
106
|
+
context.load_cert_chain(cert_file, key_file)
|
107
|
+
context.load_verify_locations(ca_cert_file)
|
108
|
+
context.verify_mode = ssl.CERT_REQUIRED
|
109
|
+
|
110
|
+
# Test connection
|
111
|
+
with socket.create_connection((host, port), timeout=5) as sock:
|
112
|
+
with context.wrap_socket(sock, server_hostname=host) as ssock:
|
113
|
+
print(f"✅ mTLS connection successful")
|
114
|
+
print(f"🔒 Cipher: {ssock.cipher()}")
|
115
|
+
print(f"📜 Protocol: {ssock.version()}")
|
116
|
+
return True
|
117
|
+
|
118
|
+
except Exception as e:
|
119
|
+
print(f"❌ mTLS connection failed: {e}")
|
120
|
+
return False
|
121
|
+
|
122
|
+
def run_mtls_application(config_path: str):
|
123
|
+
"""Run the mTLS application example."""
|
124
|
+
print("🚀 Starting mTLS Full Application Example")
|
125
|
+
print(f"📁 Configuration: {config_path}")
|
126
|
+
|
127
|
+
# Validate configuration
|
128
|
+
if not validate_mtls_config(config_path):
|
129
|
+
print("❌ mTLS Configuration validation failed")
|
130
|
+
return False
|
131
|
+
|
132
|
+
# Load configuration
|
133
|
+
try:
|
134
|
+
with open(config_path, 'r') as f:
|
135
|
+
config = json.load(f)
|
136
|
+
|
137
|
+
server_config = config.get('server', {})
|
138
|
+
host = server_config.get('host', '0.0.0.0')
|
139
|
+
port = server_config.get('port', 8443)
|
140
|
+
protocol = server_config.get('protocol', 'https')
|
141
|
+
|
142
|
+
ssl_config = config.get('ssl', {})
|
143
|
+
proxy_config = config.get('proxy_registration', {})
|
144
|
+
|
145
|
+
print(f"🌐 Server: {host}:{port}")
|
146
|
+
print(f"🔗 Protocol: {protocol}")
|
147
|
+
print(f"🔒 SSL: {'Enabled' if ssl_config.get('enabled', False) else 'Disabled'}")
|
148
|
+
print(f"🔐 mTLS: {'Enabled' if ssl_config.get('verify_client', False) else 'Disabled'}")
|
149
|
+
print(f"🔒 Security: {'Enabled' if config.get('security', {}).get('enabled', False) else 'Disabled'}")
|
150
|
+
print(f"👥 Roles: {'Enabled' if config.get('roles', {}).get('enabled', False) else 'Disabled'}")
|
151
|
+
print(f"🌐 Proxy Registration: {'Enabled' if proxy_config.get('enabled', False) else 'Disabled'}")
|
152
|
+
|
153
|
+
if proxy_config.get('enabled', False):
|
154
|
+
proxy_url = proxy_config.get('proxy_url', 'https://localhost:3004')
|
155
|
+
server_id = proxy_config.get('server_id', 'unknown')
|
156
|
+
print(f"📡 Proxy URL: {proxy_url}")
|
157
|
+
print(f"🆔 Server ID: {server_id}")
|
158
|
+
|
159
|
+
# Simulate application startup
|
160
|
+
print("\n🔧 Setting up mTLS application components...")
|
161
|
+
print("✅ Configuration loaded and validated")
|
162
|
+
print("✅ SSL/TLS certificates loaded")
|
163
|
+
print("✅ mTLS context created")
|
164
|
+
print("✅ Logging configured")
|
165
|
+
print("✅ Command registry initialized")
|
166
|
+
print("✅ Transport layer configured with mTLS")
|
167
|
+
print("✅ Security layer configured")
|
168
|
+
print("✅ Proxy registration configured")
|
169
|
+
|
170
|
+
if proxy_config.get('enabled', False):
|
171
|
+
print("✅ Proxy registration enabled")
|
172
|
+
print(f"📡 Will register with proxy at: {proxy_config.get('proxy_url')}")
|
173
|
+
print(f"🆔 Server ID: {proxy_config.get('server_id')}")
|
174
|
+
|
175
|
+
print(f"\n🎉 mTLS Full Application Example started successfully!")
|
176
|
+
print(f"📡 Server listening on {host}:{port}")
|
177
|
+
print(f"🌐 Access via: {protocol}://{host}:{port}")
|
178
|
+
print("\n📋 Available features:")
|
179
|
+
print(" - Built-in commands (health, echo, list, help)")
|
180
|
+
print(" - Custom commands (custom_echo, dynamic_calculator)")
|
181
|
+
print(" - Application hooks")
|
182
|
+
print(" - Command hooks")
|
183
|
+
print(" - Proxy endpoints")
|
184
|
+
print(" - mTLS authentication")
|
185
|
+
print(" - Security (if enabled)")
|
186
|
+
print(" - Role management (if enabled)")
|
187
|
+
|
188
|
+
print("\n🔐 mTLS Configuration:")
|
189
|
+
print(f" - Certificate: {ssl_config.get('cert_file')}")
|
190
|
+
print(f" - Private Key: {ssl_config.get('key_file')}")
|
191
|
+
print(f" - CA Certificate: {ssl_config.get('ca_cert_file')}")
|
192
|
+
print(f" - Client Verification: {'Required' if ssl_config.get('verify_client', False) else 'Optional'}")
|
193
|
+
print(f" - Ciphers: {ssl_config.get('ciphers', 'Default')}")
|
194
|
+
print(f" - Protocols: {ssl_config.get('protocols', 'Default')}")
|
195
|
+
|
196
|
+
print("\n✅ mTLS Application simulation completed successfully")
|
197
|
+
print("💡 In a real application, the mTLS server would be running here")
|
198
|
+
return True
|
199
|
+
|
200
|
+
except Exception as e:
|
201
|
+
print(f"❌ Application startup error: {e}")
|
202
|
+
return False
|
203
|
+
|
204
|
+
def main():
|
205
|
+
"""Main function."""
|
206
|
+
parser = argparse.ArgumentParser(
|
207
|
+
description="Run mTLS Full Application Example",
|
208
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
209
|
+
epilog="""
|
210
|
+
Examples:
|
211
|
+
python run_mtls.py --config configs/mtls_no_roles_correct.json
|
212
|
+
python run_mtls.py --config configs/mtls_with_roles_correct.json
|
213
|
+
python run_mtls.py --config configs/mtls_no_roles_correct.json --test-connection
|
214
|
+
"""
|
215
|
+
)
|
216
|
+
|
217
|
+
parser.add_argument(
|
218
|
+
"--config",
|
219
|
+
default="configs/mtls_no_roles_correct.json",
|
220
|
+
help="Configuration file path"
|
221
|
+
)
|
222
|
+
|
223
|
+
parser.add_argument(
|
224
|
+
"--test-connection",
|
225
|
+
action="store_true",
|
226
|
+
help="Test mTLS connection"
|
227
|
+
)
|
228
|
+
|
229
|
+
args = parser.parse_args()
|
230
|
+
|
231
|
+
# Check if config file exists
|
232
|
+
config_path = Path(args.config)
|
233
|
+
if not config_path.exists():
|
234
|
+
print(f"❌ Configuration file not found: {config_path}")
|
235
|
+
print("💡 Available mTLS configurations:")
|
236
|
+
configs_dir = Path("configs")
|
237
|
+
if configs_dir.exists():
|
238
|
+
for config_file in configs_dir.glob("*mtls*.json"):
|
239
|
+
print(f" - {config_file}")
|
240
|
+
return 1
|
241
|
+
|
242
|
+
# Test connection if requested
|
243
|
+
if args.test_connection:
|
244
|
+
success = test_mtls_connection(str(config_path))
|
245
|
+
return 0 if success else 1
|
246
|
+
|
247
|
+
# Run application
|
248
|
+
success = run_mtls_application(str(config_path))
|
249
|
+
return 0 if success else 1
|
250
|
+
|
251
|
+
if __name__ == "__main__":
|
252
|
+
sys.exit(main())
|
@@ -0,0 +1,152 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Simple Full Application Runner
|
4
|
+
Runs the full application example without complex imports.
|
5
|
+
|
6
|
+
Author: Vasiliy Zdanovskiy
|
7
|
+
email: vasilyvz@gmail.com
|
8
|
+
"""
|
9
|
+
|
10
|
+
import sys
|
11
|
+
import argparse
|
12
|
+
import logging
|
13
|
+
import json
|
14
|
+
from pathlib import Path
|
15
|
+
|
16
|
+
# Add the framework to the path
|
17
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
18
|
+
|
19
|
+
def validate_config(config_path: str) -> bool:
|
20
|
+
"""Validate configuration file."""
|
21
|
+
try:
|
22
|
+
with open(config_path, 'r') as f:
|
23
|
+
config = json.load(f)
|
24
|
+
|
25
|
+
# Check required sections
|
26
|
+
required_sections = [
|
27
|
+
'uuid', 'server', 'logging', 'commands', 'transport',
|
28
|
+
'proxy_registration', 'debug', 'security', 'roles'
|
29
|
+
]
|
30
|
+
|
31
|
+
missing_sections = []
|
32
|
+
for section in required_sections:
|
33
|
+
if section not in config:
|
34
|
+
missing_sections.append(section)
|
35
|
+
|
36
|
+
if missing_sections:
|
37
|
+
print(f"❌ Missing required sections: {missing_sections}")
|
38
|
+
return False
|
39
|
+
|
40
|
+
# Check server section
|
41
|
+
server = config.get('server', {})
|
42
|
+
if 'host' not in server or 'port' not in server:
|
43
|
+
print("❌ Server section missing host or port")
|
44
|
+
return False
|
45
|
+
|
46
|
+
print("✅ Configuration validation passed")
|
47
|
+
return True
|
48
|
+
|
49
|
+
except FileNotFoundError:
|
50
|
+
print(f"❌ Configuration file not found: {config_path}")
|
51
|
+
return False
|
52
|
+
except json.JSONDecodeError as e:
|
53
|
+
print(f"❌ Invalid JSON in configuration: {e}")
|
54
|
+
return False
|
55
|
+
except Exception as e:
|
56
|
+
print(f"❌ Configuration validation error: {e}")
|
57
|
+
return False
|
58
|
+
|
59
|
+
def run_application(config_path: str):
|
60
|
+
"""Run the full application example."""
|
61
|
+
print("🚀 Starting Full Application Example")
|
62
|
+
print(f"📁 Configuration: {config_path}")
|
63
|
+
|
64
|
+
# Validate configuration
|
65
|
+
if not validate_config(config_path):
|
66
|
+
print("❌ Configuration validation failed")
|
67
|
+
return False
|
68
|
+
|
69
|
+
# Load configuration
|
70
|
+
try:
|
71
|
+
with open(config_path, 'r') as f:
|
72
|
+
config = json.load(f)
|
73
|
+
|
74
|
+
server_config = config.get('server', {})
|
75
|
+
host = server_config.get('host', '0.0.0.0')
|
76
|
+
port = server_config.get('port', 8000)
|
77
|
+
protocol = server_config.get('protocol', 'http')
|
78
|
+
|
79
|
+
print(f"🌐 Server: {host}:{port}")
|
80
|
+
print(f"🔗 Protocol: {protocol}")
|
81
|
+
print(f"🔒 Security: {'Enabled' if config.get('security', {}).get('enabled', False) else 'Disabled'}")
|
82
|
+
print(f"👥 Roles: {'Enabled' if config.get('roles', {}).get('enabled', False) else 'Disabled'}")
|
83
|
+
|
84
|
+
# Simulate application startup
|
85
|
+
print("\n🔧 Setting up application components...")
|
86
|
+
print("✅ Configuration loaded")
|
87
|
+
print("✅ Logging configured")
|
88
|
+
print("✅ Command registry initialized")
|
89
|
+
print("✅ Transport layer configured")
|
90
|
+
print("✅ Security layer configured")
|
91
|
+
print("✅ Proxy registration configured")
|
92
|
+
|
93
|
+
print(f"\n🎉 Full Application Example started successfully!")
|
94
|
+
print(f"📡 Server listening on {host}:{port}")
|
95
|
+
print(f"🌐 Access via: {protocol}://{host}:{port}")
|
96
|
+
print("\n📋 Available features:")
|
97
|
+
print(" - Built-in commands (health, echo, list, help)")
|
98
|
+
print(" - Custom commands (custom_echo, dynamic_calculator)")
|
99
|
+
print(" - Application hooks")
|
100
|
+
print(" - Command hooks")
|
101
|
+
print(" - Proxy endpoints")
|
102
|
+
print(" - Security (if enabled)")
|
103
|
+
print(" - Role management (if enabled)")
|
104
|
+
|
105
|
+
print("\n🛑 Press Ctrl+C to stop the server")
|
106
|
+
|
107
|
+
# Simulate running server (non-blocking)
|
108
|
+
print("✅ Server simulation completed successfully")
|
109
|
+
print("💡 In a real application, the server would be running here")
|
110
|
+
return True
|
111
|
+
|
112
|
+
except Exception as e:
|
113
|
+
print(f"❌ Application startup error: {e}")
|
114
|
+
return False
|
115
|
+
|
116
|
+
def main():
|
117
|
+
"""Main function."""
|
118
|
+
parser = argparse.ArgumentParser(
|
119
|
+
description="Run Full Application Example",
|
120
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
121
|
+
epilog="""
|
122
|
+
Examples:
|
123
|
+
python run_simple.py --config configs/http_simple_correct.json
|
124
|
+
python run_simple.py --config configs/http_auth_correct.json
|
125
|
+
"""
|
126
|
+
)
|
127
|
+
|
128
|
+
parser.add_argument(
|
129
|
+
"--config",
|
130
|
+
default="configs/http_simple_correct.json",
|
131
|
+
help="Configuration file path"
|
132
|
+
)
|
133
|
+
|
134
|
+
args = parser.parse_args()
|
135
|
+
|
136
|
+
# Check if config file exists
|
137
|
+
config_path = Path(args.config)
|
138
|
+
if not config_path.exists():
|
139
|
+
print(f"❌ Configuration file not found: {config_path}")
|
140
|
+
print("💡 Available configurations:")
|
141
|
+
configs_dir = Path("configs")
|
142
|
+
if configs_dir.exists():
|
143
|
+
for config_file in configs_dir.glob("*.json"):
|
144
|
+
print(f" - {config_file}")
|
145
|
+
return 1
|
146
|
+
|
147
|
+
# Run application
|
148
|
+
success = run_application(str(config_path))
|
149
|
+
return 0 if success else 1
|
150
|
+
|
151
|
+
if __name__ == "__main__":
|
152
|
+
sys.exit(main())
|
@@ -0,0 +1,221 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Test mTLS Server for Full Application Example
|
4
|
+
Simple HTTPS server with mTLS for testing curl commands.
|
5
|
+
|
6
|
+
Author: Vasiliy Zdanovskiy
|
7
|
+
email: vasilyvz@gmail.com
|
8
|
+
"""
|
9
|
+
|
10
|
+
import sys
|
11
|
+
import json
|
12
|
+
import ssl
|
13
|
+
import socket
|
14
|
+
import threading
|
15
|
+
import time
|
16
|
+
from pathlib import Path
|
17
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
18
|
+
|
19
|
+
class TestMTLSHandler(BaseHTTPRequestHandler):
|
20
|
+
"""Test handler for mTLS requests."""
|
21
|
+
|
22
|
+
def do_GET(self):
|
23
|
+
"""Handle GET requests."""
|
24
|
+
if self.path == '/health':
|
25
|
+
self.send_response(200)
|
26
|
+
self.send_header('Content-type', 'application/json')
|
27
|
+
self.end_headers()
|
28
|
+
response = {
|
29
|
+
"status": "healthy",
|
30
|
+
"service": "mcp_proxy_adapter",
|
31
|
+
"version": "6.2.33",
|
32
|
+
"protocol": "mTLS",
|
33
|
+
"timestamp": time.time()
|
34
|
+
}
|
35
|
+
self.wfile.write(json.dumps(response).encode())
|
36
|
+
elif self.path == '/echo':
|
37
|
+
self.send_response(200)
|
38
|
+
self.send_header('Content-type', 'application/json')
|
39
|
+
self.end_headers()
|
40
|
+
response = {
|
41
|
+
"message": "Echo from mTLS server",
|
42
|
+
"timestamp": time.time(),
|
43
|
+
"client_cert": self.get_client_cert_info()
|
44
|
+
}
|
45
|
+
self.wfile.write(json.dumps(response).encode())
|
46
|
+
else:
|
47
|
+
self.send_response(404)
|
48
|
+
self.send_header('Content-type', 'application/json')
|
49
|
+
self.end_headers()
|
50
|
+
response = {"error": "Not found", "path": self.path}
|
51
|
+
self.wfile.write(json.dumps(response).encode())
|
52
|
+
|
53
|
+
def do_POST(self):
|
54
|
+
"""Handle POST requests."""
|
55
|
+
content_length = int(self.headers.get('Content-Length', 0))
|
56
|
+
post_data = self.rfile.read(content_length)
|
57
|
+
|
58
|
+
try:
|
59
|
+
data = json.loads(post_data.decode())
|
60
|
+
except json.JSONDecodeError:
|
61
|
+
data = {"raw": post_data.decode()}
|
62
|
+
|
63
|
+
self.send_response(200)
|
64
|
+
self.send_header('Content-type', 'application/json')
|
65
|
+
self.end_headers()
|
66
|
+
|
67
|
+
response = {
|
68
|
+
"received": data,
|
69
|
+
"timestamp": time.time(),
|
70
|
+
"client_cert": self.get_client_cert_info(),
|
71
|
+
"method": "POST"
|
72
|
+
}
|
73
|
+
self.wfile.write(json.dumps(response).encode())
|
74
|
+
|
75
|
+
def get_client_cert_info(self):
|
76
|
+
"""Get client certificate information."""
|
77
|
+
try:
|
78
|
+
cert = self.connection.getpeercert()
|
79
|
+
if cert:
|
80
|
+
return {
|
81
|
+
"subject": dict(x[0] for x in cert.get('subject', [])),
|
82
|
+
"issuer": dict(x[0] for x in cert.get('issuer', [])),
|
83
|
+
"serial": cert.get('serialNumber'),
|
84
|
+
"not_before": cert.get('notBefore'),
|
85
|
+
"not_after": cert.get('notAfter')
|
86
|
+
}
|
87
|
+
except Exception:
|
88
|
+
pass
|
89
|
+
return None
|
90
|
+
|
91
|
+
def log_message(self, format, *args):
|
92
|
+
"""Override log message to include client cert info."""
|
93
|
+
client_cert = self.get_client_cert_info()
|
94
|
+
if client_cert:
|
95
|
+
print(f"[{self.address_string()}] {format % args} [Client: {client_cert.get('subject', {}).get('CN', 'Unknown')}]")
|
96
|
+
else:
|
97
|
+
print(f"[{self.address_string()}] {format % args} [No client cert]")
|
98
|
+
|
99
|
+
class TestMTLSServer:
|
100
|
+
"""Test mTLS server for the full application example."""
|
101
|
+
|
102
|
+
def __init__(self, config_path: str):
|
103
|
+
self.config_path = config_path
|
104
|
+
self.config = self.load_config()
|
105
|
+
self.server = None
|
106
|
+
self.thread = None
|
107
|
+
|
108
|
+
def load_config(self):
|
109
|
+
"""Load configuration from file."""
|
110
|
+
with open(self.config_path, 'r') as f:
|
111
|
+
return json.load(f)
|
112
|
+
|
113
|
+
def create_ssl_context(self):
|
114
|
+
"""Create SSL context for mTLS."""
|
115
|
+
ssl_config = self.config.get('ssl', {})
|
116
|
+
|
117
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
118
|
+
context.load_cert_chain(
|
119
|
+
ssl_config.get('cert_file'),
|
120
|
+
ssl_config.get('key_file')
|
121
|
+
)
|
122
|
+
|
123
|
+
# Load CA certificate for client verification
|
124
|
+
if ssl_config.get('verify_client', False):
|
125
|
+
context.load_verify_locations(ssl_config.get('ca_cert_file'))
|
126
|
+
context.verify_mode = ssl.CERT_REQUIRED
|
127
|
+
|
128
|
+
# Set ciphers and protocols
|
129
|
+
if ssl_config.get('ciphers'):
|
130
|
+
context.set_ciphers(ssl_config['ciphers'])
|
131
|
+
|
132
|
+
return context
|
133
|
+
|
134
|
+
def start_server(self):
|
135
|
+
"""Start the mTLS server."""
|
136
|
+
server_config = self.config.get('server', {})
|
137
|
+
host = server_config.get('host', '0.0.0.0')
|
138
|
+
port = server_config.get('port', 8443)
|
139
|
+
|
140
|
+
print(f"🚀 Starting mTLS Test Server")
|
141
|
+
print(f"📁 Configuration: {self.config_path}")
|
142
|
+
print(f"🌐 Server: {host}:{port}")
|
143
|
+
print(f"🔐 mTLS: {'Enabled' if self.config.get('ssl', {}).get('verify_client', False) else 'Disabled'}")
|
144
|
+
|
145
|
+
# Create server
|
146
|
+
self.server = HTTPServer((host, port), TestMTLSHandler)
|
147
|
+
|
148
|
+
# Configure SSL
|
149
|
+
ssl_context = self.create_ssl_context()
|
150
|
+
self.server.socket = ssl_context.wrap_socket(
|
151
|
+
self.server.socket,
|
152
|
+
server_side=True
|
153
|
+
)
|
154
|
+
|
155
|
+
print(f"✅ mTLS Test Server started on {host}:{port}")
|
156
|
+
print(f"🔐 SSL Context configured")
|
157
|
+
print(f"📜 Available endpoints:")
|
158
|
+
print(f" - GET /health - Health check")
|
159
|
+
print(f" - GET /echo - Echo test")
|
160
|
+
print(f" - POST /echo - Echo with data")
|
161
|
+
print(f"\n🛑 Press Ctrl+C to stop the server")
|
162
|
+
|
163
|
+
try:
|
164
|
+
self.server.serve_forever()
|
165
|
+
except KeyboardInterrupt:
|
166
|
+
print(f"\n🛑 Server stopped by user")
|
167
|
+
self.server.shutdown()
|
168
|
+
|
169
|
+
def start_background(self):
|
170
|
+
"""Start server in background thread."""
|
171
|
+
self.thread = threading.Thread(target=self.start_server, daemon=True)
|
172
|
+
self.thread.start()
|
173
|
+
time.sleep(1) # Give server time to start
|
174
|
+
return self.thread.is_alive()
|
175
|
+
|
176
|
+
def stop_server(self):
|
177
|
+
"""Stop the server."""
|
178
|
+
if self.server:
|
179
|
+
self.server.shutdown()
|
180
|
+
if self.thread:
|
181
|
+
self.thread.join(timeout=5)
|
182
|
+
|
183
|
+
def main():
|
184
|
+
"""Main function."""
|
185
|
+
import argparse
|
186
|
+
|
187
|
+
parser = argparse.ArgumentParser(description="Test mTLS Server")
|
188
|
+
parser.add_argument("--config", required=True, help="Configuration file path")
|
189
|
+
parser.add_argument("--background", action="store_true", help="Run in background")
|
190
|
+
args = parser.parse_args()
|
191
|
+
|
192
|
+
# Check if config file exists
|
193
|
+
config_path = Path(args.config)
|
194
|
+
if not config_path.exists():
|
195
|
+
print(f"❌ Configuration file not found: {config_path}")
|
196
|
+
return 1
|
197
|
+
|
198
|
+
# Create and start server
|
199
|
+
server = TestMTLSServer(str(config_path))
|
200
|
+
|
201
|
+
if args.background:
|
202
|
+
print("🔄 Starting server in background...")
|
203
|
+
if server.start_background():
|
204
|
+
print("✅ Server started in background")
|
205
|
+
print("💡 Use Ctrl+C to stop")
|
206
|
+
try:
|
207
|
+
while True:
|
208
|
+
time.sleep(1)
|
209
|
+
except KeyboardInterrupt:
|
210
|
+
print("\n🛑 Stopping background server...")
|
211
|
+
server.stop_server()
|
212
|
+
else:
|
213
|
+
print("❌ Failed to start server in background")
|
214
|
+
return 1
|
215
|
+
else:
|
216
|
+
server.start_server()
|
217
|
+
|
218
|
+
return 0
|
219
|
+
|
220
|
+
if __name__ == "__main__":
|
221
|
+
sys.exit(main())
|