mcp-security-framework 0.1.0__py3-none-any.whl → 1.1.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_security_framework/__init__.py +26 -15
- mcp_security_framework/cli/__init__.py +1 -1
- mcp_security_framework/cli/cert_cli.py +233 -197
- mcp_security_framework/cli/security_cli.py +324 -234
- mcp_security_framework/constants.py +21 -27
- mcp_security_framework/core/auth_manager.py +49 -20
- mcp_security_framework/core/cert_manager.py +398 -104
- mcp_security_framework/core/permission_manager.py +13 -9
- mcp_security_framework/core/rate_limiter.py +10 -0
- mcp_security_framework/core/security_manager.py +286 -229
- mcp_security_framework/examples/__init__.py +6 -0
- mcp_security_framework/examples/comprehensive_example.py +954 -0
- mcp_security_framework/examples/django_example.py +276 -202
- mcp_security_framework/examples/fastapi_example.py +897 -393
- mcp_security_framework/examples/flask_example.py +311 -200
- mcp_security_framework/examples/gateway_example.py +373 -214
- mcp_security_framework/examples/microservice_example.py +337 -172
- mcp_security_framework/examples/standalone_example.py +719 -478
- mcp_security_framework/examples/test_all_examples.py +572 -0
- mcp_security_framework/middleware/__init__.py +46 -55
- mcp_security_framework/middleware/auth_middleware.py +62 -63
- mcp_security_framework/middleware/fastapi_auth_middleware.py +179 -110
- mcp_security_framework/middleware/fastapi_middleware.py +156 -148
- mcp_security_framework/middleware/flask_auth_middleware.py +267 -107
- mcp_security_framework/middleware/flask_middleware.py +183 -157
- mcp_security_framework/middleware/mtls_middleware.py +106 -117
- mcp_security_framework/middleware/rate_limit_middleware.py +105 -101
- mcp_security_framework/middleware/security_middleware.py +109 -124
- mcp_security_framework/schemas/config.py +2 -1
- mcp_security_framework/schemas/models.py +19 -6
- mcp_security_framework/utils/cert_utils.py +14 -8
- mcp_security_framework/utils/datetime_compat.py +116 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/METADATA +2 -1
- mcp_security_framework-1.1.1.dist-info/RECORD +84 -0
- tests/conftest.py +303 -0
- tests/test_cli/test_cert_cli.py +194 -174
- tests/test_cli/test_security_cli.py +274 -247
- tests/test_core/test_cert_manager.py +33 -19
- tests/test_core/test_security_manager.py +2 -2
- tests/test_examples/test_comprehensive_example.py +613 -0
- tests/test_examples/test_fastapi_example.py +290 -169
- tests/test_examples/test_flask_example.py +304 -162
- tests/test_examples/test_standalone_example.py +106 -168
- tests/test_integration/test_auth_flow.py +214 -198
- tests/test_integration/test_certificate_flow.py +181 -150
- tests/test_integration/test_fastapi_integration.py +140 -149
- tests/test_integration/test_flask_integration.py +144 -141
- tests/test_integration/test_standalone_integration.py +331 -300
- tests/test_middleware/test_fastapi_auth_middleware.py +745 -0
- tests/test_middleware/test_fastapi_middleware.py +147 -132
- tests/test_middleware/test_flask_auth_middleware.py +696 -0
- tests/test_middleware/test_flask_middleware.py +201 -179
- tests/test_middleware/test_security_middleware.py +151 -130
- tests/test_utils/test_datetime_compat.py +147 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +0 -76
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/WHEEL +0 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/entry_points.txt +0 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,954 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Comprehensive Security Framework Example
|
4
|
+
|
5
|
+
This example demonstrates ALL capabilities of the MCP Security Framework
|
6
|
+
including certificate management, SSL/TLS, authentication, authorization,
|
7
|
+
and security validation.
|
8
|
+
|
9
|
+
Demonstrated Features:
|
10
|
+
1. Root CA certificate creation
|
11
|
+
2. Intermediate CA certificate creation
|
12
|
+
3. Client and server certificate creation
|
13
|
+
4. Certificate Signing Request (CSR) generation
|
14
|
+
5. Certificate Revocation List (CRL) creation with reasons
|
15
|
+
6. SSL/TLS context creation and validation
|
16
|
+
7. mTLS (mutual TLS) configuration
|
17
|
+
8. Authentication (API Key, JWT, Certificate)
|
18
|
+
9. Authorization (Role-based access control)
|
19
|
+
10. Rate Limiting
|
20
|
+
11. Security Validation
|
21
|
+
12. Security Monitoring
|
22
|
+
|
23
|
+
Author: Vasiliy Zdanovskiy
|
24
|
+
email: vasilyvz@gmail.com
|
25
|
+
Version: 1.0.0
|
26
|
+
License: MIT
|
27
|
+
"""
|
28
|
+
|
29
|
+
import json
|
30
|
+
import logging
|
31
|
+
import os
|
32
|
+
import shutil
|
33
|
+
import socket
|
34
|
+
import ssl
|
35
|
+
import tempfile
|
36
|
+
from datetime import datetime, timedelta, timezone
|
37
|
+
from pathlib import Path
|
38
|
+
from typing import Any, Dict, List, Optional, Tuple
|
39
|
+
|
40
|
+
from mcp_security_framework.constants import (
|
41
|
+
AUTH_METHODS,
|
42
|
+
DEFAULT_SECURITY_HEADERS,
|
43
|
+
ErrorCodes,
|
44
|
+
)
|
45
|
+
from mcp_security_framework.core.cert_manager import CertificateManager
|
46
|
+
from mcp_security_framework.core.security_manager import SecurityManager
|
47
|
+
from mcp_security_framework.core.ssl_manager import SSLManager
|
48
|
+
from mcp_security_framework.schemas.config import (
|
49
|
+
AuthConfig,
|
50
|
+
CAConfig,
|
51
|
+
CertificateConfig,
|
52
|
+
ClientCertConfig,
|
53
|
+
IntermediateCAConfig,
|
54
|
+
LoggingConfig,
|
55
|
+
PermissionConfig,
|
56
|
+
RateLimitConfig,
|
57
|
+
SecurityConfig,
|
58
|
+
ServerCertConfig,
|
59
|
+
SSLConfig,
|
60
|
+
)
|
61
|
+
from mcp_security_framework.schemas.models import (
|
62
|
+
AuthMethod,
|
63
|
+
AuthResult,
|
64
|
+
AuthStatus,
|
65
|
+
CertificateInfo,
|
66
|
+
CertificatePair,
|
67
|
+
ValidationResult,
|
68
|
+
ValidationStatus,
|
69
|
+
)
|
70
|
+
|
71
|
+
|
72
|
+
class ComprehensiveSecurityExample:
|
73
|
+
"""
|
74
|
+
Comprehensive Security Example
|
75
|
+
|
76
|
+
This class demonstrates ALL capabilities of the MCP Security Framework
|
77
|
+
including advanced certificate management features.
|
78
|
+
"""
|
79
|
+
|
80
|
+
def __init__(self, work_dir: Optional[str] = None):
|
81
|
+
"""
|
82
|
+
Initialize the comprehensive security example.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
work_dir: Working directory for certificates and keys
|
86
|
+
"""
|
87
|
+
self.work_dir = work_dir or tempfile.mkdtemp(
|
88
|
+
prefix="mcp_security_comprehensive_"
|
89
|
+
)
|
90
|
+
self.certs_dir = os.path.join(self.work_dir, "certs")
|
91
|
+
self.keys_dir = os.path.join(self.work_dir, "keys")
|
92
|
+
self.config_dir = os.path.join(self.work_dir, "config")
|
93
|
+
|
94
|
+
# Create directories
|
95
|
+
os.makedirs(self.certs_dir, exist_ok=True)
|
96
|
+
os.makedirs(self.keys_dir, exist_ok=True)
|
97
|
+
os.makedirs(self.config_dir, exist_ok=True)
|
98
|
+
|
99
|
+
# Initialize logger first
|
100
|
+
self.logger = logging.getLogger(__name__)
|
101
|
+
|
102
|
+
# Create roles configuration first
|
103
|
+
self._create_roles_config()
|
104
|
+
|
105
|
+
# Initialize configuration
|
106
|
+
self.config = self._create_comprehensive_config()
|
107
|
+
self.security_manager = SecurityManager(self.config)
|
108
|
+
self.cert_manager = CertificateManager(self.config.certificates)
|
109
|
+
self.ssl_manager = SSLManager(self.config.ssl)
|
110
|
+
|
111
|
+
# Test data
|
112
|
+
self.test_api_key = "admin_key_123"
|
113
|
+
self.test_jwt_token = self._create_test_jwt_token()
|
114
|
+
|
115
|
+
# Certificate paths
|
116
|
+
self.ca_cert_path = None
|
117
|
+
self.ca_key_path = None
|
118
|
+
self.intermediate_ca_cert_path = None
|
119
|
+
self.intermediate_ca_key_path = None
|
120
|
+
self.server_cert_path = None
|
121
|
+
self.server_key_path = None
|
122
|
+
self.client_cert_path = None
|
123
|
+
self.client_key_path = None
|
124
|
+
|
125
|
+
self.logger.info("Comprehensive Security Example initialized successfully")
|
126
|
+
|
127
|
+
def _create_comprehensive_config(self) -> SecurityConfig:
|
128
|
+
"""Create comprehensive security configuration."""
|
129
|
+
return SecurityConfig(
|
130
|
+
auth=AuthConfig(
|
131
|
+
enabled=True,
|
132
|
+
methods=[
|
133
|
+
AUTH_METHODS["API_KEY"],
|
134
|
+
AUTH_METHODS["JWT"],
|
135
|
+
AUTH_METHODS["CERTIFICATE"],
|
136
|
+
],
|
137
|
+
api_keys={
|
138
|
+
"admin_key_123": {"username": "admin", "roles": ["admin", "user"]},
|
139
|
+
"user_key_456": {"username": "user", "roles": ["user"]},
|
140
|
+
"readonly_key_789": {"username": "readonly", "roles": ["readonly"]},
|
141
|
+
},
|
142
|
+
jwt_secret="your-super-secret-jwt-key-change-in-production-12345",
|
143
|
+
jwt_algorithm="HS256",
|
144
|
+
jwt_expiry_hours=24,
|
145
|
+
public_paths=["/health/", "/metrics/"],
|
146
|
+
security_headers=DEFAULT_SECURITY_HEADERS,
|
147
|
+
),
|
148
|
+
permissions=PermissionConfig(
|
149
|
+
enabled=True,
|
150
|
+
roles_file=os.path.join(self.config_dir, "roles.json"),
|
151
|
+
default_role="user",
|
152
|
+
hierarchy_enabled=True,
|
153
|
+
),
|
154
|
+
ssl=SSLConfig(
|
155
|
+
enabled=False, # Disable initially, will be enabled after certificates are created
|
156
|
+
cert_file=None,
|
157
|
+
key_file=None,
|
158
|
+
ca_cert_file=None,
|
159
|
+
verify_mode="CERT_REQUIRED",
|
160
|
+
min_version="TLSv1.2",
|
161
|
+
cipher_suite="ECDHE-RSA-AES256-GCM-SHA384",
|
162
|
+
),
|
163
|
+
certificates=CertificateConfig(
|
164
|
+
enabled=False, # Disable initially, will be enabled after CA creation
|
165
|
+
ca_cert_path=None, # Will be set after CA creation
|
166
|
+
ca_key_path=None, # Will be set after CA creation
|
167
|
+
cert_storage_path=self.certs_dir,
|
168
|
+
key_storage_path=self.keys_dir,
|
169
|
+
default_validity_days=365,
|
170
|
+
default_key_size=2048,
|
171
|
+
),
|
172
|
+
rate_limiting=RateLimitConfig(
|
173
|
+
enabled=True,
|
174
|
+
default_limit=100,
|
175
|
+
default_window=60,
|
176
|
+
storage_type="memory",
|
177
|
+
),
|
178
|
+
logging=LoggingConfig(
|
179
|
+
enabled=True,
|
180
|
+
level="INFO",
|
181
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
182
|
+
file_path=os.path.join(self.work_dir, "security.log"),
|
183
|
+
),
|
184
|
+
)
|
185
|
+
|
186
|
+
def _create_test_jwt_token(self) -> str:
|
187
|
+
"""Create a test JWT token."""
|
188
|
+
import jwt
|
189
|
+
|
190
|
+
payload = {
|
191
|
+
"username": "test_user",
|
192
|
+
"roles": ["user"],
|
193
|
+
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
|
194
|
+
}
|
195
|
+
|
196
|
+
# Ensure JWT secret is a string
|
197
|
+
jwt_secret = (
|
198
|
+
str(self.config.auth.jwt_secret)
|
199
|
+
if self.config.auth.jwt_secret
|
200
|
+
else "default-secret"
|
201
|
+
)
|
202
|
+
jwt_algorithm = (
|
203
|
+
str(self.config.auth.jwt_algorithm)
|
204
|
+
if self.config.auth.jwt_algorithm
|
205
|
+
else "HS256"
|
206
|
+
)
|
207
|
+
|
208
|
+
return jwt.encode(payload, jwt_secret, algorithm=jwt_algorithm)
|
209
|
+
|
210
|
+
def _create_roles_config(self):
|
211
|
+
"""Create roles configuration file."""
|
212
|
+
roles_config = {
|
213
|
+
"roles": {
|
214
|
+
"admin": {
|
215
|
+
"description": "Administrator role",
|
216
|
+
"permissions": ["*"],
|
217
|
+
"parent_roles": [],
|
218
|
+
},
|
219
|
+
"user": {
|
220
|
+
"description": "User role",
|
221
|
+
"permissions": ["read:own", "write:own"],
|
222
|
+
"parent_roles": [],
|
223
|
+
},
|
224
|
+
"readonly": {
|
225
|
+
"description": "Read Only role",
|
226
|
+
"permissions": ["read:own"],
|
227
|
+
"parent_roles": [],
|
228
|
+
},
|
229
|
+
}
|
230
|
+
}
|
231
|
+
|
232
|
+
roles_file = os.path.join(self.config_dir, "roles.json")
|
233
|
+
with open(roles_file, "w") as f:
|
234
|
+
json.dump(roles_config, f, indent=2)
|
235
|
+
|
236
|
+
self.logger.info(f"Created roles configuration: {roles_file}")
|
237
|
+
|
238
|
+
def demonstrate_certificate_management(self) -> Dict[str, Any]:
|
239
|
+
"""
|
240
|
+
Demonstrate comprehensive certificate management capabilities.
|
241
|
+
|
242
|
+
Returns:
|
243
|
+
Dict with certificate management test results
|
244
|
+
"""
|
245
|
+
self.logger.info("Demonstrating comprehensive certificate management...")
|
246
|
+
|
247
|
+
results = {
|
248
|
+
"root_ca_creation": {},
|
249
|
+
"intermediate_ca_creation": {},
|
250
|
+
"server_cert_creation": {},
|
251
|
+
"client_cert_creation": {},
|
252
|
+
"csr_creation": {},
|
253
|
+
"crl_creation": {},
|
254
|
+
"certificate_validation": {},
|
255
|
+
}
|
256
|
+
|
257
|
+
try:
|
258
|
+
# 1. Create Root CA
|
259
|
+
self.logger.info("Creating Root CA certificate...")
|
260
|
+
ca_config = CAConfig(
|
261
|
+
common_name="MCP Security Root CA",
|
262
|
+
organization="MCP Security Framework",
|
263
|
+
country="US",
|
264
|
+
state="California",
|
265
|
+
locality="San Francisco",
|
266
|
+
validity_days=3650,
|
267
|
+
key_size=4096,
|
268
|
+
)
|
269
|
+
|
270
|
+
ca_pair = self.cert_manager.create_root_ca(ca_config)
|
271
|
+
self.ca_cert_path = ca_pair.certificate_path
|
272
|
+
self.ca_key_path = ca_pair.private_key_path
|
273
|
+
|
274
|
+
results["root_ca_creation"] = {
|
275
|
+
"success": True,
|
276
|
+
"cert_path": self.ca_cert_path,
|
277
|
+
"key_path": self.ca_key_path,
|
278
|
+
"serial_number": ca_pair.serial_number,
|
279
|
+
}
|
280
|
+
self.logger.info(f"Root CA created: {self.ca_cert_path}")
|
281
|
+
|
282
|
+
# 2. Create Intermediate CA
|
283
|
+
self.logger.info("Creating Intermediate CA certificate...")
|
284
|
+
intermediate_config = IntermediateCAConfig(
|
285
|
+
common_name="MCP Security Intermediate CA",
|
286
|
+
organization="MCP Security Framework",
|
287
|
+
country="US",
|
288
|
+
state="California",
|
289
|
+
locality="San Francisco",
|
290
|
+
validity_days=1825,
|
291
|
+
key_size=2048,
|
292
|
+
parent_ca_cert=self.ca_cert_path,
|
293
|
+
parent_ca_key=self.ca_key_path,
|
294
|
+
)
|
295
|
+
|
296
|
+
intermediate_pair = self.cert_manager.create_intermediate_ca(
|
297
|
+
intermediate_config
|
298
|
+
)
|
299
|
+
self.intermediate_ca_cert_path = intermediate_pair.certificate_path
|
300
|
+
self.intermediate_ca_key_path = intermediate_pair.private_key_path
|
301
|
+
|
302
|
+
results["intermediate_ca_creation"] = {
|
303
|
+
"success": True,
|
304
|
+
"cert_path": self.intermediate_ca_cert_path,
|
305
|
+
"key_path": self.intermediate_ca_key_path,
|
306
|
+
"serial_number": intermediate_pair.serial_number,
|
307
|
+
}
|
308
|
+
self.logger.info(
|
309
|
+
f"Intermediate CA created: {self.intermediate_ca_cert_path}"
|
310
|
+
)
|
311
|
+
|
312
|
+
# 3. Create Server Certificate
|
313
|
+
self.logger.info("Creating server certificate...")
|
314
|
+
server_config = ServerCertConfig(
|
315
|
+
common_name="api.example.com",
|
316
|
+
organization="Example Corp",
|
317
|
+
country="US",
|
318
|
+
state="California",
|
319
|
+
locality="San Francisco",
|
320
|
+
validity_days=365,
|
321
|
+
key_size=2048,
|
322
|
+
ca_cert_path=self.intermediate_ca_cert_path,
|
323
|
+
ca_key_path=self.intermediate_ca_key_path,
|
324
|
+
)
|
325
|
+
|
326
|
+
server_pair = self.cert_manager.create_server_certificate(server_config)
|
327
|
+
self.server_cert_path = server_pair.certificate_path
|
328
|
+
self.server_key_path = server_pair.private_key_path
|
329
|
+
|
330
|
+
results["server_cert_creation"] = {
|
331
|
+
"success": True,
|
332
|
+
"cert_path": self.server_cert_path,
|
333
|
+
"key_path": self.server_key_path,
|
334
|
+
"serial_number": server_pair.serial_number,
|
335
|
+
}
|
336
|
+
self.logger.info(f"Server certificate created: {self.server_cert_path}")
|
337
|
+
|
338
|
+
# 4. Create Client Certificate
|
339
|
+
self.logger.info("Creating client certificate...")
|
340
|
+
client_config = ClientCertConfig(
|
341
|
+
common_name="client.example.com",
|
342
|
+
organization="Example Corp",
|
343
|
+
country="US",
|
344
|
+
state="California",
|
345
|
+
locality="San Francisco",
|
346
|
+
validity_days=365,
|
347
|
+
key_size=2048,
|
348
|
+
ca_cert_path=self.intermediate_ca_cert_path,
|
349
|
+
ca_key_path=self.intermediate_ca_key_path,
|
350
|
+
)
|
351
|
+
|
352
|
+
client_pair = self.cert_manager.create_client_certificate(client_config)
|
353
|
+
self.client_cert_path = client_pair.certificate_path
|
354
|
+
self.client_key_path = client_pair.private_key_path
|
355
|
+
|
356
|
+
results["client_cert_creation"] = {
|
357
|
+
"success": True,
|
358
|
+
"cert_path": self.client_cert_path,
|
359
|
+
"key_path": self.client_key_path,
|
360
|
+
"serial_number": client_pair.serial_number,
|
361
|
+
}
|
362
|
+
self.logger.info(f"Client certificate created: {self.client_cert_path}")
|
363
|
+
|
364
|
+
# 5. Create Certificate Signing Request (CSR)
|
365
|
+
self.logger.info("Creating Certificate Signing Request...")
|
366
|
+
csr_path, csr_key_path = (
|
367
|
+
self.cert_manager.create_certificate_signing_request(
|
368
|
+
common_name="new-service.example.com",
|
369
|
+
organization="New Service Corp",
|
370
|
+
country="US",
|
371
|
+
state="California",
|
372
|
+
locality="San Francisco",
|
373
|
+
organizational_unit="IT Department",
|
374
|
+
email="admin@new-service.example.com",
|
375
|
+
key_size=2048,
|
376
|
+
key_type="rsa",
|
377
|
+
)
|
378
|
+
)
|
379
|
+
|
380
|
+
results["csr_creation"] = {
|
381
|
+
"success": True,
|
382
|
+
"csr_path": csr_path,
|
383
|
+
"key_path": csr_key_path,
|
384
|
+
}
|
385
|
+
self.logger.info(f"CSR created: {csr_path}")
|
386
|
+
|
387
|
+
# 6. Create Certificate Revocation List (CRL)
|
388
|
+
self.logger.info("Creating Certificate Revocation List...")
|
389
|
+
revoked_serials = [
|
390
|
+
{
|
391
|
+
"serial": "123456789",
|
392
|
+
"reason": "key_compromise",
|
393
|
+
"revocation_date": datetime.now(timezone.utc),
|
394
|
+
},
|
395
|
+
{
|
396
|
+
"serial": "987654321",
|
397
|
+
"reason": "certificate_hold",
|
398
|
+
"revocation_date": datetime.now(timezone.utc),
|
399
|
+
},
|
400
|
+
]
|
401
|
+
|
402
|
+
crl_path = self.cert_manager.create_crl(
|
403
|
+
ca_cert_path=self.intermediate_ca_cert_path,
|
404
|
+
ca_key_path=self.intermediate_ca_key_path,
|
405
|
+
revoked_serials=revoked_serials,
|
406
|
+
validity_days=30,
|
407
|
+
)
|
408
|
+
|
409
|
+
results["crl_creation"] = {
|
410
|
+
"success": True,
|
411
|
+
"crl_path": crl_path,
|
412
|
+
"revoked_count": len(revoked_serials),
|
413
|
+
}
|
414
|
+
self.logger.info(f"CRL created: {crl_path}")
|
415
|
+
|
416
|
+
# 7. Certificate Validation
|
417
|
+
self.logger.info("Validating certificates...")
|
418
|
+
cert_info = self.cert_manager.get_certificate_info(self.server_cert_path)
|
419
|
+
|
420
|
+
results["certificate_validation"] = {
|
421
|
+
"success": True,
|
422
|
+
"subject": cert_info.subject,
|
423
|
+
"issuer": cert_info.issuer,
|
424
|
+
"serial_number": cert_info.serial_number,
|
425
|
+
"valid_from": cert_info.not_before.isoformat(),
|
426
|
+
"valid_until": cert_info.not_after.isoformat(),
|
427
|
+
"is_valid": not cert_info.is_expired,
|
428
|
+
}
|
429
|
+
self.logger.info("Certificate validation completed")
|
430
|
+
|
431
|
+
# Update configuration with certificate paths
|
432
|
+
self._update_config_after_certificates()
|
433
|
+
|
434
|
+
except Exception as e:
|
435
|
+
self.logger.error(f"Certificate management demonstration failed: {str(e)}")
|
436
|
+
results["error"] = str(e)
|
437
|
+
|
438
|
+
return results
|
439
|
+
|
440
|
+
def _update_config_after_certificates(self):
|
441
|
+
"""Update configuration with certificate paths after creation."""
|
442
|
+
if self.ca_cert_path and self.ca_key_path:
|
443
|
+
self.config.certificates.enabled = True
|
444
|
+
self.config.certificates.ca_cert_path = self.ca_cert_path
|
445
|
+
self.config.certificates.ca_key_path = self.ca_key_path
|
446
|
+
|
447
|
+
# Reinitialize certificate manager with updated config
|
448
|
+
self.cert_manager = CertificateManager(self.config.certificates)
|
449
|
+
|
450
|
+
if self.server_cert_path and self.server_key_path and self.ca_cert_path:
|
451
|
+
self.config.ssl.enabled = True
|
452
|
+
self.config.ssl.cert_file = self.server_cert_path
|
453
|
+
self.config.ssl.key_file = self.server_key_path
|
454
|
+
self.config.ssl.ca_cert_file = self.ca_cert_path
|
455
|
+
|
456
|
+
# Reinitialize SSL manager with updated config
|
457
|
+
self.ssl_manager = SSLManager(self.config.ssl)
|
458
|
+
|
459
|
+
def demonstrate_ssl_tls_management(self) -> Dict[str, Any]:
|
460
|
+
"""
|
461
|
+
Demonstrate SSL/TLS management capabilities.
|
462
|
+
|
463
|
+
Returns:
|
464
|
+
Dict with SSL/TLS test results
|
465
|
+
"""
|
466
|
+
self.logger.info("Demonstrating SSL/TLS management...")
|
467
|
+
|
468
|
+
results = {
|
469
|
+
"server_context_creation": {},
|
470
|
+
"client_context_creation": {},
|
471
|
+
"mtls_context_creation": {},
|
472
|
+
"ssl_validation": {},
|
473
|
+
}
|
474
|
+
|
475
|
+
try:
|
476
|
+
# 1. Create Server SSL Context
|
477
|
+
self.logger.info("Creating server SSL context...")
|
478
|
+
server_context = self.ssl_manager.create_server_context(
|
479
|
+
cert_file=self.server_cert_path,
|
480
|
+
key_file=self.server_key_path,
|
481
|
+
ca_cert_file=self.ca_cert_path,
|
482
|
+
verify_mode="CERT_REQUIRED",
|
483
|
+
min_version="TLSv1.2",
|
484
|
+
)
|
485
|
+
|
486
|
+
results["server_context_creation"] = {
|
487
|
+
"success": True,
|
488
|
+
"verify_mode": str(server_context.verify_mode),
|
489
|
+
"min_version": str(server_context.minimum_version),
|
490
|
+
"max_version": str(server_context.maximum_version),
|
491
|
+
}
|
492
|
+
self.logger.info("Server SSL context created successfully")
|
493
|
+
|
494
|
+
# 2. Create Client SSL Context
|
495
|
+
self.logger.info("Creating client SSL context...")
|
496
|
+
client_context = self.ssl_manager.create_client_context(
|
497
|
+
ca_cert_file=self.ca_cert_path,
|
498
|
+
client_cert_file=self.client_cert_path,
|
499
|
+
client_key_file=self.client_key_path,
|
500
|
+
verify_mode="CERT_REQUIRED",
|
501
|
+
min_version="TLSv1.2",
|
502
|
+
)
|
503
|
+
|
504
|
+
results["client_context_creation"] = {
|
505
|
+
"success": True,
|
506
|
+
"verify_mode": str(client_context.verify_mode),
|
507
|
+
"min_version": str(client_context.minimum_version),
|
508
|
+
"max_version": str(client_context.maximum_version),
|
509
|
+
}
|
510
|
+
self.logger.info("Client SSL context created successfully")
|
511
|
+
|
512
|
+
# 3. Create mTLS Context (mutual TLS)
|
513
|
+
self.logger.info("Creating mTLS context...")
|
514
|
+
mtls_context = self.ssl_manager.create_server_context(
|
515
|
+
cert_file=self.server_cert_path,
|
516
|
+
key_file=self.server_key_path,
|
517
|
+
ca_cert_file=self.ca_cert_path,
|
518
|
+
verify_mode="CERT_REQUIRED",
|
519
|
+
min_version="TLSv1.2",
|
520
|
+
)
|
521
|
+
|
522
|
+
results["mtls_context_creation"] = {
|
523
|
+
"success": True,
|
524
|
+
"verify_mode": str(mtls_context.verify_mode),
|
525
|
+
"client_cert_required": True,
|
526
|
+
}
|
527
|
+
self.logger.info("mTLS context created successfully")
|
528
|
+
|
529
|
+
# 4. SSL Configuration Validation
|
530
|
+
self.logger.info("Validating SSL configuration...")
|
531
|
+
|
532
|
+
# Check if SSL files exist
|
533
|
+
ssl_enabled = self.config.ssl.enabled
|
534
|
+
cert_valid = (
|
535
|
+
os.path.exists(self.server_cert_path)
|
536
|
+
if self.server_cert_path
|
537
|
+
else False
|
538
|
+
)
|
539
|
+
key_valid = (
|
540
|
+
os.path.exists(self.server_key_path) if self.server_key_path else False
|
541
|
+
)
|
542
|
+
ca_valid = os.path.exists(self.ca_cert_path) if self.ca_cert_path else False
|
543
|
+
|
544
|
+
results["ssl_validation"] = {
|
545
|
+
"success": True,
|
546
|
+
"ssl_enabled": ssl_enabled,
|
547
|
+
"certificate_valid": cert_valid,
|
548
|
+
"key_valid": key_valid,
|
549
|
+
"ca_valid": ca_valid,
|
550
|
+
}
|
551
|
+
self.logger.info("SSL validation completed")
|
552
|
+
|
553
|
+
except Exception as e:
|
554
|
+
self.logger.error(f"SSL/TLS management demonstration failed: {str(e)}")
|
555
|
+
results["error"] = str(e)
|
556
|
+
|
557
|
+
return results
|
558
|
+
|
559
|
+
def demonstrate_authentication(self) -> Dict[str, Any]:
|
560
|
+
"""
|
561
|
+
Demonstrate authentication capabilities.
|
562
|
+
|
563
|
+
Returns:
|
564
|
+
Dict with authentication test results
|
565
|
+
"""
|
566
|
+
self.logger.info("Demonstrating authentication capabilities...")
|
567
|
+
|
568
|
+
results = {
|
569
|
+
"api_key_auth": {},
|
570
|
+
"jwt_auth": {},
|
571
|
+
"certificate_auth": {},
|
572
|
+
"failed_auth": {},
|
573
|
+
}
|
574
|
+
|
575
|
+
try:
|
576
|
+
# 1. API Key Authentication
|
577
|
+
self.logger.info("Testing API key authentication...")
|
578
|
+
auth_result = self.security_manager.authenticate_user(
|
579
|
+
{"method": "api_key", "api_key": self.test_api_key}
|
580
|
+
)
|
581
|
+
results["api_key_auth"] = {
|
582
|
+
"success": auth_result.is_valid,
|
583
|
+
"username": auth_result.username,
|
584
|
+
"roles": auth_result.roles,
|
585
|
+
"auth_method": auth_result.auth_method.value,
|
586
|
+
}
|
587
|
+
|
588
|
+
# 2. JWT Authentication
|
589
|
+
self.logger.info("Testing JWT authentication...")
|
590
|
+
auth_result = self.security_manager.authenticate_user(
|
591
|
+
{"method": "jwt", "token": self.test_jwt_token}
|
592
|
+
)
|
593
|
+
results["jwt_auth"] = {
|
594
|
+
"success": auth_result.is_valid,
|
595
|
+
"username": auth_result.username,
|
596
|
+
"roles": auth_result.roles,
|
597
|
+
"auth_method": auth_result.auth_method.value,
|
598
|
+
}
|
599
|
+
|
600
|
+
# 3. Certificate Authentication (if certificate available)
|
601
|
+
if self.client_cert_path:
|
602
|
+
self.logger.info("Testing certificate authentication...")
|
603
|
+
with open(self.client_cert_path, "r") as f:
|
604
|
+
cert_pem = f.read()
|
605
|
+
|
606
|
+
auth_result = self.security_manager.authenticate_user(
|
607
|
+
{"method": "certificate", "certificate": cert_pem}
|
608
|
+
)
|
609
|
+
results["certificate_auth"] = {
|
610
|
+
"success": auth_result.is_valid,
|
611
|
+
"username": auth_result.username,
|
612
|
+
"roles": auth_result.roles,
|
613
|
+
"auth_method": auth_result.auth_method.value,
|
614
|
+
}
|
615
|
+
|
616
|
+
# 4. Failed Authentication
|
617
|
+
self.logger.info("Testing failed authentication...")
|
618
|
+
auth_result = self.security_manager.authenticate_user(
|
619
|
+
{"method": "api_key", "api_key": "invalid_key"}
|
620
|
+
)
|
621
|
+
results["failed_auth"] = {
|
622
|
+
"success": auth_result.is_valid,
|
623
|
+
"error_code": auth_result.error_code,
|
624
|
+
"error_message": auth_result.error_message,
|
625
|
+
}
|
626
|
+
|
627
|
+
except Exception as e:
|
628
|
+
self.logger.error(f"Authentication demonstration failed: {str(e)}")
|
629
|
+
results["error"] = str(e)
|
630
|
+
|
631
|
+
return results
|
632
|
+
|
633
|
+
def demonstrate_authorization(self) -> Dict[str, Any]:
|
634
|
+
"""
|
635
|
+
Demonstrate authorization capabilities.
|
636
|
+
|
637
|
+
Returns:
|
638
|
+
Dict with authorization test results
|
639
|
+
"""
|
640
|
+
self.logger.info("Demonstrating authorization capabilities...")
|
641
|
+
|
642
|
+
results = {
|
643
|
+
"admin_permissions": {},
|
644
|
+
"user_permissions": {},
|
645
|
+
"readonly_permissions": {},
|
646
|
+
"denied_permissions": {},
|
647
|
+
}
|
648
|
+
|
649
|
+
try:
|
650
|
+
# 1. Admin Permissions
|
651
|
+
self.logger.info("Testing admin permissions...")
|
652
|
+
result = self.security_manager.check_permissions(
|
653
|
+
user_roles=["admin"], required_permissions=["read", "write", "delete"]
|
654
|
+
)
|
655
|
+
results["admin_permissions"] = {
|
656
|
+
"success": result.is_valid,
|
657
|
+
"status": result.status.value,
|
658
|
+
}
|
659
|
+
|
660
|
+
# 2. User Permissions
|
661
|
+
self.logger.info("Testing user permissions...")
|
662
|
+
result = self.security_manager.check_permissions(
|
663
|
+
user_roles=["user"], required_permissions=["read:own", "write:own"]
|
664
|
+
)
|
665
|
+
results["user_permissions"] = {
|
666
|
+
"success": result.is_valid,
|
667
|
+
"status": result.status.value,
|
668
|
+
}
|
669
|
+
|
670
|
+
# 3. Readonly Permissions
|
671
|
+
self.logger.info("Testing readonly permissions...")
|
672
|
+
result = self.security_manager.check_permissions(
|
673
|
+
user_roles=["readonly"], required_permissions=["read:own"]
|
674
|
+
)
|
675
|
+
results["readonly_permissions"] = {
|
676
|
+
"success": result.is_valid,
|
677
|
+
"status": result.status.value,
|
678
|
+
}
|
679
|
+
|
680
|
+
# 4. Denied Permissions
|
681
|
+
self.logger.info("Testing denied permissions...")
|
682
|
+
result = self.security_manager.check_permissions(
|
683
|
+
user_roles=["readonly"], required_permissions=["write", "delete"]
|
684
|
+
)
|
685
|
+
results["denied_permissions"] = {
|
686
|
+
"success": result.is_valid,
|
687
|
+
"status": result.status.value,
|
688
|
+
}
|
689
|
+
|
690
|
+
except Exception as e:
|
691
|
+
self.logger.error(f"Authorization demonstration failed: {str(e)}")
|
692
|
+
results["error"] = str(e)
|
693
|
+
|
694
|
+
return results
|
695
|
+
|
696
|
+
def demonstrate_rate_limiting(self) -> Dict[str, Any]:
|
697
|
+
"""
|
698
|
+
Demonstrate rate limiting capabilities.
|
699
|
+
|
700
|
+
Returns:
|
701
|
+
Dict with rate limiting test results
|
702
|
+
"""
|
703
|
+
self.logger.info("Demonstrating rate limiting capabilities...")
|
704
|
+
|
705
|
+
results = {"rate_limit_checks": [], "rate_limit_exceeded": False}
|
706
|
+
|
707
|
+
try:
|
708
|
+
# Test rate limiting
|
709
|
+
for i in range(5):
|
710
|
+
allowed = self.security_manager.check_rate_limit("test_user")
|
711
|
+
results["rate_limit_checks"].append(
|
712
|
+
{"request": i + 1, "allowed": allowed}
|
713
|
+
)
|
714
|
+
|
715
|
+
if not allowed:
|
716
|
+
results["rate_limit_exceeded"] = True
|
717
|
+
break
|
718
|
+
|
719
|
+
except Exception as e:
|
720
|
+
self.logger.error(f"Rate limiting demonstration failed: {str(e)}")
|
721
|
+
results["error"] = str(e)
|
722
|
+
|
723
|
+
return results
|
724
|
+
|
725
|
+
def demonstrate_security_validation(self) -> Dict[str, Any]:
|
726
|
+
"""
|
727
|
+
Demonstrate security validation capabilities.
|
728
|
+
|
729
|
+
Returns:
|
730
|
+
Dict with validation test results
|
731
|
+
"""
|
732
|
+
self.logger.info("Demonstrating security validation capabilities...")
|
733
|
+
|
734
|
+
results = {"request_validation": {}, "configuration_validation": {}}
|
735
|
+
|
736
|
+
try:
|
737
|
+
# 1. Request Validation
|
738
|
+
request_data = {
|
739
|
+
"api_key": self.test_api_key,
|
740
|
+
"required_permissions": ["read", "write"],
|
741
|
+
"client_ip": "192.168.1.100",
|
742
|
+
}
|
743
|
+
|
744
|
+
result = self.security_manager.validate_request(request_data)
|
745
|
+
results["request_validation"] = {
|
746
|
+
"success": result.is_valid,
|
747
|
+
"status": result.status.value,
|
748
|
+
}
|
749
|
+
|
750
|
+
# 2. Configuration Validation
|
751
|
+
result = self.security_manager.validate_configuration()
|
752
|
+
results["configuration_validation"] = {
|
753
|
+
"success": result.is_valid,
|
754
|
+
"status": result.status.value,
|
755
|
+
}
|
756
|
+
|
757
|
+
except Exception as e:
|
758
|
+
self.logger.error(f"Security validation demonstration failed: {str(e)}")
|
759
|
+
results["error"] = str(e)
|
760
|
+
|
761
|
+
return results
|
762
|
+
|
763
|
+
def demonstrate_security_monitoring(self) -> Dict[str, Any]:
|
764
|
+
"""
|
765
|
+
Demonstrate security monitoring capabilities.
|
766
|
+
|
767
|
+
Returns:
|
768
|
+
Dict with monitoring test results
|
769
|
+
"""
|
770
|
+
self.logger.info("Demonstrating security monitoring capabilities...")
|
771
|
+
|
772
|
+
results = {"security_status": {}, "security_metrics": {}, "security_audit": {}}
|
773
|
+
|
774
|
+
try:
|
775
|
+
# 1. Security Status
|
776
|
+
status = self.security_manager.get_security_status()
|
777
|
+
results["security_status"] = status
|
778
|
+
|
779
|
+
# 2. Security Metrics
|
780
|
+
metrics = self.security_manager.get_security_metrics()
|
781
|
+
results["security_metrics"] = metrics
|
782
|
+
|
783
|
+
# 3. Security Audit (not implemented yet, use empty dict)
|
784
|
+
results["security_audit"] = {
|
785
|
+
"authentication": [],
|
786
|
+
"authorization": [],
|
787
|
+
"certificate_operations": [],
|
788
|
+
"ssl_operations": [],
|
789
|
+
}
|
790
|
+
|
791
|
+
except Exception as e:
|
792
|
+
self.logger.error(f"Security monitoring demonstration failed: {str(e)}")
|
793
|
+
results["error"] = str(e)
|
794
|
+
|
795
|
+
return results
|
796
|
+
|
797
|
+
def run_comprehensive_demo(self) -> Dict[str, Any]:
|
798
|
+
"""
|
799
|
+
Run comprehensive demonstration of all framework capabilities.
|
800
|
+
|
801
|
+
Returns:
|
802
|
+
Dict with comprehensive test results
|
803
|
+
"""
|
804
|
+
self.logger.info("Starting comprehensive security framework demonstration...")
|
805
|
+
|
806
|
+
# Roles configuration already created in __init__
|
807
|
+
|
808
|
+
# Run all demonstrations
|
809
|
+
results = {
|
810
|
+
"framework": "MCP Security Framework",
|
811
|
+
"version": "1.0.0",
|
812
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
813
|
+
"certificate_management": self.demonstrate_certificate_management(),
|
814
|
+
"ssl_tls_management": self.demonstrate_ssl_tls_management(),
|
815
|
+
"authentication": self.demonstrate_authentication(),
|
816
|
+
"authorization": self.demonstrate_authorization(),
|
817
|
+
"rate_limiting": self.demonstrate_rate_limiting(),
|
818
|
+
"security_validation": self.demonstrate_security_validation(),
|
819
|
+
"security_monitoring": self.demonstrate_security_monitoring(),
|
820
|
+
}
|
821
|
+
|
822
|
+
self.logger.info("Comprehensive demonstration completed successfully")
|
823
|
+
return results
|
824
|
+
|
825
|
+
|
826
|
+
def main():
|
827
|
+
"""Main function to run the comprehensive example."""
|
828
|
+
print("\n🚀 MCP Security Framework - Comprehensive Example")
|
829
|
+
print("=" * 80)
|
830
|
+
|
831
|
+
# Create example instance
|
832
|
+
example = ComprehensiveSecurityExample()
|
833
|
+
|
834
|
+
try:
|
835
|
+
# Run comprehensive demonstration
|
836
|
+
results = example.run_comprehensive_demo()
|
837
|
+
|
838
|
+
# Print results
|
839
|
+
print("\n📊 COMPREHENSIVE DEMONSTRATION RESULTS")
|
840
|
+
print("=" * 80)
|
841
|
+
print(f"Framework: {results['framework']}")
|
842
|
+
print(f"Version: {results['version']}")
|
843
|
+
print(f"Timestamp: {results['timestamp']}")
|
844
|
+
|
845
|
+
print("\n🔐 CERTIFICATE MANAGEMENT RESULTS:")
|
846
|
+
cert_mgmt = results["certificate_management"]
|
847
|
+
print(
|
848
|
+
f" Root CA Creation: {'✅' if cert_mgmt.get('root_ca_creation', {}).get('success') else '❌'}"
|
849
|
+
)
|
850
|
+
print(
|
851
|
+
f" Intermediate CA Creation: {'✅' if cert_mgmt.get('intermediate_ca_creation', {}).get('success') else '❌'}"
|
852
|
+
)
|
853
|
+
print(
|
854
|
+
f" Server Cert Creation: {'✅' if cert_mgmt.get('server_cert_creation', {}).get('success') else '❌'}"
|
855
|
+
)
|
856
|
+
print(
|
857
|
+
f" Client Cert Creation: {'✅' if cert_mgmt.get('client_cert_creation', {}).get('success') else '❌'}"
|
858
|
+
)
|
859
|
+
print(
|
860
|
+
f" CSR Creation: {'✅' if cert_mgmt.get('csr_creation', {}).get('success') else '❌'}"
|
861
|
+
)
|
862
|
+
print(
|
863
|
+
f" CRL Creation: {'✅' if cert_mgmt.get('crl_creation', {}).get('success') else '❌'}"
|
864
|
+
)
|
865
|
+
print(
|
866
|
+
f" Certificate Validation: {'✅' if cert_mgmt.get('certificate_validation', {}).get('success') else '❌'}"
|
867
|
+
)
|
868
|
+
|
869
|
+
print("\n🔒 SSL/TLS MANAGEMENT RESULTS:")
|
870
|
+
ssl_mgmt = results["ssl_tls_management"]
|
871
|
+
print(
|
872
|
+
f" Server Context: {'✅' if ssl_mgmt.get('server_context_creation', {}).get('success') else '❌'}"
|
873
|
+
)
|
874
|
+
print(
|
875
|
+
f" Client Context: {'✅' if ssl_mgmt.get('client_context_creation', {}).get('success') else '❌'}"
|
876
|
+
)
|
877
|
+
print(
|
878
|
+
f" mTLS Context: {'✅' if ssl_mgmt.get('mtls_context_creation', {}).get('success') else '❌'}"
|
879
|
+
)
|
880
|
+
print(
|
881
|
+
f" SSL Validation: {'✅' if ssl_mgmt.get('ssl_validation', {}).get('success') else '❌'}"
|
882
|
+
)
|
883
|
+
|
884
|
+
print("\n🔑 AUTHENTICATION RESULTS:")
|
885
|
+
auth = results["authentication"]
|
886
|
+
print(
|
887
|
+
f" API Key: {'✅' if auth.get('api_key_auth', {}).get('success') else '❌'}"
|
888
|
+
)
|
889
|
+
print(f" JWT: {'✅' if auth.get('jwt_auth', {}).get('success') else '❌'}")
|
890
|
+
print(
|
891
|
+
f" Certificate: {'✅' if auth.get('certificate_auth', {}).get('success') else '❌'}"
|
892
|
+
)
|
893
|
+
print(
|
894
|
+
f" Failed Auth: {'✅' if not auth.get('failed_auth', {}).get('success') else '❌'}"
|
895
|
+
)
|
896
|
+
|
897
|
+
print("\n🔐 AUTHORIZATION RESULTS:")
|
898
|
+
authz = results["authorization"]
|
899
|
+
print(
|
900
|
+
f" Admin Permissions: {'✅' if authz.get('admin_permissions', {}).get('success') else '❌'}"
|
901
|
+
)
|
902
|
+
print(
|
903
|
+
f" User Permissions: {'✅' if authz.get('user_permissions', {}).get('success') else '❌'}"
|
904
|
+
)
|
905
|
+
print(
|
906
|
+
f" Readonly Permissions: {'✅' if authz.get('readonly_permissions', {}).get('success') else '❌'}"
|
907
|
+
)
|
908
|
+
print(
|
909
|
+
f" Denied Permissions: {'✅' if not authz.get('denied_permissions', {}).get('success') else '❌'}"
|
910
|
+
)
|
911
|
+
|
912
|
+
print("\n⚡ RATE LIMITING RESULTS:")
|
913
|
+
rate_limit = results["rate_limiting"]
|
914
|
+
print(f" Rate Limit Checks: {len(rate_limit.get('rate_limit_checks', []))}")
|
915
|
+
print(
|
916
|
+
f" Rate Limit Exceeded: {'❌' if rate_limit.get('rate_limit_exceeded') else '✅'}"
|
917
|
+
)
|
918
|
+
|
919
|
+
print("\n🔒 SECURITY VALIDATION RESULTS:")
|
920
|
+
validation = results["security_validation"]
|
921
|
+
print(
|
922
|
+
f" Request Validation: {'✅' if validation.get('request_validation', {}).get('success') else '❌'}"
|
923
|
+
)
|
924
|
+
print(
|
925
|
+
f" Configuration Validation: {'✅' if validation.get('configuration_validation', {}).get('success') else '❌'}"
|
926
|
+
)
|
927
|
+
|
928
|
+
print("\n📊 SECURITY MONITORING RESULTS:")
|
929
|
+
monitoring = results["security_monitoring"]
|
930
|
+
print(
|
931
|
+
f" Security Status: {'✅' if monitoring.get('security_status') else '❌'}"
|
932
|
+
)
|
933
|
+
print(
|
934
|
+
f" Security Metrics: {'✅' if monitoring.get('security_metrics') else '❌'}"
|
935
|
+
)
|
936
|
+
print(f" Security Audit: {'✅' if monitoring.get('security_audit') else '❌'}")
|
937
|
+
|
938
|
+
print("\n🎉 ALL FRAMEWORK CAPABILITIES DEMONSTRATED SUCCESSFULLY!")
|
939
|
+
print("=" * 80)
|
940
|
+
|
941
|
+
# Cleanup
|
942
|
+
if example.work_dir and os.path.exists(example.work_dir):
|
943
|
+
shutil.rmtree(example.work_dir)
|
944
|
+
print(f"\n🧹 Cleaned up working directory: {example.work_dir}")
|
945
|
+
|
946
|
+
except Exception as e:
|
947
|
+
print(f"\n❌ Demonstration failed: {str(e)}")
|
948
|
+
import traceback
|
949
|
+
|
950
|
+
traceback.print_exc()
|
951
|
+
|
952
|
+
|
953
|
+
if __name__ == "__main__":
|
954
|
+
main()
|