mcp-proxy-adapter 6.6.0__py3-none-any.whl → 6.6.3__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/app.py +28 -26
- mcp_proxy_adapter/config.py +2 -9
- mcp_proxy_adapter/core/server_adapter.py +1 -1
- mcp_proxy_adapter/examples/check_config.py +415 -0
- mcp_proxy_adapter/examples/config_builder.py +11 -17
- mcp_proxy_adapter/examples/{generate_certificates_bugfix.py → generate_certificates.py} +11 -0
- mcp_proxy_adapter/examples/generate_config.py +343 -0
- mcp_proxy_adapter/examples/run_full_test_suite.py +3 -3
- mcp_proxy_adapter/examples/security_test_client.py +6 -5
- mcp_proxy_adapter/examples/test_chk_hostname_automated.py +211 -0
- mcp_proxy_adapter/examples/test_framework_complete.py +269 -0
- mcp_proxy_adapter/examples/test_mcp_server.py +188 -0
- mcp_proxy_adapter/main.py +11 -18
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.6.0.dist-info → mcp_proxy_adapter-6.6.3.dist-info}/METADATA +1 -1
- {mcp_proxy_adapter-6.6.0.dist-info → mcp_proxy_adapter-6.6.3.dist-info}/RECORD +19 -20
- mcp_proxy_adapter/examples/config_builder_simple.py +0 -271
- mcp_proxy_adapter/examples/generate_all_certificates.py +0 -487
- mcp_proxy_adapter/examples/generate_certificates_cli.py +0 -406
- mcp_proxy_adapter/examples/generate_certificates_fixed.py +0 -313
- mcp_proxy_adapter/examples/generate_certificates_framework.py +0 -366
- mcp_proxy_adapter/examples/generate_certificates_openssl.py +0 -391
- {mcp_proxy_adapter-6.6.0.dist-info → mcp_proxy_adapter-6.6.3.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.6.0.dist-info → mcp_proxy_adapter-6.6.3.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.6.0.dist-info → mcp_proxy_adapter-6.6.3.dist-info}/top_level.txt +0 -0
@@ -1,487 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
Generate All Certificates for Security Testing
|
4
|
-
This script generates all necessary certificates for comprehensive security testing:
|
5
|
-
- Root CA certificate and key
|
6
|
-
- Server certificates for HTTPS and mTLS
|
7
|
-
- Client certificates for different roles (admin, user, readonly, etc.)
|
8
|
-
- Test certificates for negative scenarios
|
9
|
-
Author: Vasiliy Zdanovskiy
|
10
|
-
email: vasilyvz@gmail.com
|
11
|
-
"""
|
12
|
-
import json
|
13
|
-
import os
|
14
|
-
import subprocess
|
15
|
-
import sys
|
16
|
-
from pathlib import Path
|
17
|
-
from typing import Dict, List, Optional
|
18
|
-
|
19
|
-
|
20
|
-
class CertificateGenerator:
|
21
|
-
"""Generate all certificates for security testing."""
|
22
|
-
|
23
|
-
def __init__(self):
|
24
|
-
self.project_root = Path(__file__).parent.parent.parent
|
25
|
-
self.certs_dir = self.project_root / "mcp_proxy_adapter" / "examples" / "certs"
|
26
|
-
self.keys_dir = self.project_root / "mcp_proxy_adapter" / "examples" / "keys"
|
27
|
-
# Create directories if they don't exist
|
28
|
-
self.certs_dir.mkdir(parents=True, exist_ok=True)
|
29
|
-
self.keys_dir.mkdir(parents=True, exist_ok=True)
|
30
|
-
# Certificate configuration
|
31
|
-
self.ca_config = {
|
32
|
-
"common_name": "MCP Proxy Adapter Test CA",
|
33
|
-
"organization": "Test Organization",
|
34
|
-
"country": "US",
|
35
|
-
"state": "Test State",
|
36
|
-
"city": "Test City",
|
37
|
-
"validity_years": 10,
|
38
|
-
}
|
39
|
-
self.server_config = {
|
40
|
-
"common_name": "mcp-proxy-adapter-test.local",
|
41
|
-
"organization": "Test Organization",
|
42
|
-
"country": "US",
|
43
|
-
"state": "Test State",
|
44
|
-
"city": "Test City",
|
45
|
-
"validity_years": 2,
|
46
|
-
"san": ["localhost", "127.0.0.1", "mcp-proxy-adapter-test.local"],
|
47
|
-
}
|
48
|
-
# Client certificates configuration
|
49
|
-
self.client_certs = {
|
50
|
-
"admin": {
|
51
|
-
"common_name": "admin-client",
|
52
|
-
"organization": "Test Organization",
|
53
|
-
"roles": ["admin"],
|
54
|
-
"permissions": ["*"],
|
55
|
-
},
|
56
|
-
"user": {
|
57
|
-
"common_name": "user-client",
|
58
|
-
"organization": "Test Organization",
|
59
|
-
"roles": ["user"],
|
60
|
-
"permissions": ["read", "write"],
|
61
|
-
},
|
62
|
-
"readonly": {
|
63
|
-
"common_name": "readonly-client",
|
64
|
-
"organization": "Test Organization",
|
65
|
-
"roles": ["readonly"],
|
66
|
-
"permissions": ["read"],
|
67
|
-
},
|
68
|
-
"guest": {
|
69
|
-
"common_name": "guest-client",
|
70
|
-
"organization": "Test Organization",
|
71
|
-
"roles": ["guest"],
|
72
|
-
"permissions": ["read"],
|
73
|
-
},
|
74
|
-
"proxy": {
|
75
|
-
"common_name": "proxy-client",
|
76
|
-
"organization": "Test Organization",
|
77
|
-
"roles": ["proxy"],
|
78
|
-
"permissions": ["register", "discover"],
|
79
|
-
},
|
80
|
-
}
|
81
|
-
# Negative test certificates
|
82
|
-
self.negative_certs = {
|
83
|
-
"expired": {
|
84
|
-
"common_name": "expired-client",
|
85
|
-
"organization": "Test Organization",
|
86
|
-
"validity_days": 1, # Will expire quickly
|
87
|
-
},
|
88
|
-
"wrong_org": {
|
89
|
-
"common_name": "wrong-org-client",
|
90
|
-
"organization": "Wrong Organization",
|
91
|
-
"roles": ["user"],
|
92
|
-
},
|
93
|
-
"no_roles": {
|
94
|
-
"common_name": "no-roles-client",
|
95
|
-
"organization": "Test Organization",
|
96
|
-
"roles": [],
|
97
|
-
},
|
98
|
-
"invalid_roles": {
|
99
|
-
"common_name": "invalid-roles-client",
|
100
|
-
"organization": "Test Organization",
|
101
|
-
"roles": ["invalid_role"],
|
102
|
-
},
|
103
|
-
}
|
104
|
-
|
105
|
-
def run_command(self, cmd: List[str], description: str) -> bool:
|
106
|
-
"""Run a command and handle errors."""
|
107
|
-
try:
|
108
|
-
print(f"🔧 {description}...")
|
109
|
-
result = subprocess.run(
|
110
|
-
cmd, cwd=self.project_root, capture_output=True, text=True, check=True
|
111
|
-
)
|
112
|
-
print(f"✅ {description} completed successfully")
|
113
|
-
return True
|
114
|
-
except subprocess.CalledProcessError as e:
|
115
|
-
print(f"❌ {description} failed:")
|
116
|
-
print(f" Command: {' '.join(cmd)}")
|
117
|
-
print(f" Error: {e.stderr}")
|
118
|
-
return False
|
119
|
-
except Exception as e:
|
120
|
-
print(f"❌ {description} failed: {e}")
|
121
|
-
return False
|
122
|
-
|
123
|
-
def create_ca_certificate(self) -> bool:
|
124
|
-
"""Create Root CA certificate and key."""
|
125
|
-
ca_cert_path = self.certs_dir / "ca_cert.pem"
|
126
|
-
ca_key_path = self.keys_dir / "ca_key.pem"
|
127
|
-
if ca_cert_path.exists() and ca_key_path.exists():
|
128
|
-
print(f"ℹ️ CA certificate already exists: {ca_cert_path}")
|
129
|
-
return True
|
130
|
-
cmd = [
|
131
|
-
sys.executable,
|
132
|
-
"-m",
|
133
|
-
"mcp_security_framework.cli.cert_cli",
|
134
|
-
"create-ca",
|
135
|
-
"-cn",
|
136
|
-
self.ca_config["common_name"],
|
137
|
-
"-o",
|
138
|
-
self.ca_config["organization"],
|
139
|
-
"-c",
|
140
|
-
self.ca_config["country"],
|
141
|
-
"-s",
|
142
|
-
self.ca_config["state"],
|
143
|
-
"-l",
|
144
|
-
self.ca_config["city"],
|
145
|
-
"-y",
|
146
|
-
str(self.ca_config["validity_years"]),
|
147
|
-
]
|
148
|
-
success = self.run_command(cmd, "Creating Root CA certificate")
|
149
|
-
if success:
|
150
|
-
# Move files to correct locations
|
151
|
-
default_ca_cert = (
|
152
|
-
Path("./certs")
|
153
|
-
/ f"{self.ca_config['common_name'].lower().replace(' ', '_')}_ca.crt"
|
154
|
-
)
|
155
|
-
default_ca_key = (
|
156
|
-
Path("./keys")
|
157
|
-
/ f"{self.ca_config['common_name'].lower().replace(' ', '_')}_ca.key"
|
158
|
-
)
|
159
|
-
if default_ca_cert.exists():
|
160
|
-
self.run_command(
|
161
|
-
["mv", str(default_ca_cert), str(ca_cert_path)],
|
162
|
-
"Moving CA certificate",
|
163
|
-
)
|
164
|
-
if default_ca_key.exists():
|
165
|
-
self.run_command(
|
166
|
-
["mv", str(default_ca_key), str(ca_key_path)], "Moving CA key"
|
167
|
-
)
|
168
|
-
return success
|
169
|
-
|
170
|
-
def create_server_certificate(self) -> bool:
|
171
|
-
"""Create server certificate for HTTPS and mTLS."""
|
172
|
-
server_cert_path = self.certs_dir / "server_cert.pem"
|
173
|
-
server_key_path = self.certs_dir / "server_key.pem"
|
174
|
-
if server_cert_path.exists() and server_key_path.exists():
|
175
|
-
print(f"ℹ️ Server certificate already exists: {server_cert_path}")
|
176
|
-
return True
|
177
|
-
# Create server certificate
|
178
|
-
cmd = [
|
179
|
-
sys.executable,
|
180
|
-
"-m",
|
181
|
-
"mcp_security_framework.cli.cert_cli",
|
182
|
-
"create-server",
|
183
|
-
"-cn",
|
184
|
-
self.server_config["common_name"],
|
185
|
-
"-o",
|
186
|
-
self.server_config["organization"],
|
187
|
-
"-c",
|
188
|
-
self.server_config["country"],
|
189
|
-
"-s",
|
190
|
-
self.server_config["state"],
|
191
|
-
"-l",
|
192
|
-
self.server_config["city"],
|
193
|
-
"-d",
|
194
|
-
str(self.server_config["validity_years"] * 365), # Convert years to days
|
195
|
-
]
|
196
|
-
# Add SAN if supported
|
197
|
-
if self.server_config["san"]:
|
198
|
-
for san in self.server_config["san"]:
|
199
|
-
cmd.extend(["--san", san])
|
200
|
-
success = self.run_command(cmd, "Creating server certificate")
|
201
|
-
if success:
|
202
|
-
# Move files to correct locations
|
203
|
-
default_server_cert = (
|
204
|
-
Path("./certs")
|
205
|
-
/ f"{self.server_config['common_name'].lower().replace('.', '_')}_server.crt"
|
206
|
-
)
|
207
|
-
default_server_key = (
|
208
|
-
Path("./keys")
|
209
|
-
/ f"{self.server_config['common_name'].lower().replace('.', '_')}_server.key"
|
210
|
-
)
|
211
|
-
if default_server_cert.exists():
|
212
|
-
self.run_command(
|
213
|
-
["mv", str(default_server_cert), str(server_cert_path)],
|
214
|
-
"Moving server certificate",
|
215
|
-
)
|
216
|
-
if default_server_key.exists():
|
217
|
-
self.run_command(
|
218
|
-
["mv", str(default_server_key), str(server_key_path)],
|
219
|
-
"Moving server key",
|
220
|
-
)
|
221
|
-
return success
|
222
|
-
|
223
|
-
def create_client_certificate(self, name: str, config: Dict) -> bool:
|
224
|
-
"""Create client certificate with specific configuration."""
|
225
|
-
cert_path = self.certs_dir / f"{name}_cert.pem"
|
226
|
-
key_path = self.certs_dir / f"{name}_key.pem"
|
227
|
-
if cert_path.exists() and key_path.exists():
|
228
|
-
print(f"ℹ️ Client certificate {name} already exists: {cert_path}")
|
229
|
-
return True
|
230
|
-
cmd = [
|
231
|
-
sys.executable,
|
232
|
-
"-m",
|
233
|
-
"mcp_security_framework.cli.cert_cli",
|
234
|
-
"create-client",
|
235
|
-
"-cn",
|
236
|
-
config["common_name"],
|
237
|
-
"-o",
|
238
|
-
config["organization"],
|
239
|
-
"-c",
|
240
|
-
self.ca_config["country"],
|
241
|
-
"-s",
|
242
|
-
self.ca_config["state"],
|
243
|
-
"-l",
|
244
|
-
self.ca_config["city"],
|
245
|
-
"-d",
|
246
|
-
"730", # 2 years in days
|
247
|
-
]
|
248
|
-
# Add roles if specified
|
249
|
-
if "roles" in config and config["roles"]:
|
250
|
-
for role in config["roles"]:
|
251
|
-
cmd.extend(["--roles", role])
|
252
|
-
# Add permissions if specified
|
253
|
-
if "permissions" in config and config["permissions"]:
|
254
|
-
for permission in config["permissions"]:
|
255
|
-
cmd.extend(["--permissions", permission])
|
256
|
-
# Add custom validity for negative tests
|
257
|
-
if "validity_days" in config:
|
258
|
-
cmd[cmd.index("-d") + 1] = str(config["validity_days"])
|
259
|
-
success = self.run_command(cmd, f"Creating client certificate: {name}")
|
260
|
-
if success:
|
261
|
-
# Move files to correct locations
|
262
|
-
default_client_cert = (
|
263
|
-
Path("./certs")
|
264
|
-
/ f"{config['common_name'].lower().replace('-', '_')}_client.crt"
|
265
|
-
)
|
266
|
-
default_client_key = (
|
267
|
-
Path("./keys")
|
268
|
-
/ f"{config['common_name'].lower().replace('-', '_')}_client.key"
|
269
|
-
)
|
270
|
-
if default_client_cert.exists():
|
271
|
-
self.run_command(
|
272
|
-
["mv", str(default_client_cert), str(cert_path)],
|
273
|
-
f"Moving {name} certificate",
|
274
|
-
)
|
275
|
-
if default_client_key.exists():
|
276
|
-
self.run_command(
|
277
|
-
["mv", str(default_client_key), str(key_path)], f"Moving {name} key"
|
278
|
-
)
|
279
|
-
return success
|
280
|
-
|
281
|
-
def create_legacy_certificates(self) -> bool:
|
282
|
-
"""Create legacy certificate files for compatibility."""
|
283
|
-
legacy_files = [
|
284
|
-
("client.crt", "client.key"),
|
285
|
-
("client_admin.crt", "client_admin.key"),
|
286
|
-
("admin.crt", "admin.key"),
|
287
|
-
("user.crt", "user.key"),
|
288
|
-
("readonly.crt", "readonly.key"),
|
289
|
-
]
|
290
|
-
success = True
|
291
|
-
for cert_file, key_file in legacy_files:
|
292
|
-
cert_path = self.certs_dir / cert_file
|
293
|
-
key_path = self.certs_dir / key_file
|
294
|
-
if not cert_path.exists() or not key_path.exists():
|
295
|
-
# Copy from existing certificates
|
296
|
-
if (
|
297
|
-
cert_file == "client.crt"
|
298
|
-
and (self.certs_dir / "user_cert.pem").exists()
|
299
|
-
):
|
300
|
-
self.run_command(
|
301
|
-
["cp", str(self.certs_dir / "user_cert.pem"), str(cert_path)],
|
302
|
-
f"Creating {cert_file}",
|
303
|
-
)
|
304
|
-
self.run_command(
|
305
|
-
["cp", str(self.certs_dir / "user_key.pem"), str(key_path)],
|
306
|
-
f"Creating {key_file}",
|
307
|
-
)
|
308
|
-
elif (
|
309
|
-
cert_file == "client_admin.crt"
|
310
|
-
and (self.certs_dir / "admin_cert.pem").exists()
|
311
|
-
):
|
312
|
-
self.run_command(
|
313
|
-
["cp", str(self.certs_dir / "admin_cert.pem"), str(cert_path)],
|
314
|
-
f"Creating {cert_file}",
|
315
|
-
)
|
316
|
-
self.run_command(
|
317
|
-
["cp", str(self.certs_dir / "admin_key.pem"), str(key_path)],
|
318
|
-
f"Creating {key_file}",
|
319
|
-
)
|
320
|
-
elif (
|
321
|
-
cert_file == "admin.crt"
|
322
|
-
and (self.certs_dir / "admin_cert.pem").exists()
|
323
|
-
):
|
324
|
-
self.run_command(
|
325
|
-
["cp", str(self.certs_dir / "admin_cert.pem"), str(cert_path)],
|
326
|
-
f"Creating {cert_file}",
|
327
|
-
)
|
328
|
-
self.run_command(
|
329
|
-
["cp", str(self.certs_dir / "admin_key.pem"), str(key_path)],
|
330
|
-
f"Creating {key_file}",
|
331
|
-
)
|
332
|
-
elif (
|
333
|
-
cert_file == "user.crt"
|
334
|
-
and (self.certs_dir / "user_cert.pem").exists()
|
335
|
-
):
|
336
|
-
self.run_command(
|
337
|
-
["cp", str(self.certs_dir / "user_cert.pem"), str(cert_path)],
|
338
|
-
f"Creating {cert_file}",
|
339
|
-
)
|
340
|
-
self.run_command(
|
341
|
-
["cp", str(self.certs_dir / "user_key.pem"), str(key_path)],
|
342
|
-
f"Creating {key_file}",
|
343
|
-
)
|
344
|
-
elif (
|
345
|
-
cert_file == "readonly.crt"
|
346
|
-
and (self.certs_dir / "readonly_cert.pem").exists()
|
347
|
-
):
|
348
|
-
self.run_command(
|
349
|
-
[
|
350
|
-
"cp",
|
351
|
-
str(self.certs_dir / "readonly_cert.pem"),
|
352
|
-
str(cert_path),
|
353
|
-
],
|
354
|
-
f"Creating {cert_file}",
|
355
|
-
)
|
356
|
-
self.run_command(
|
357
|
-
["cp", str(self.certs_dir / "readonly_key.pem"), str(key_path)],
|
358
|
-
f"Creating {key_file}",
|
359
|
-
)
|
360
|
-
return success
|
361
|
-
|
362
|
-
def create_certificate_config(self) -> bool:
|
363
|
-
"""Create certificate configuration file."""
|
364
|
-
config_path = self.certs_dir / "cert_config.json"
|
365
|
-
config = {
|
366
|
-
"ca_cert_path": str(self.certs_dir / "ca_cert.pem"),
|
367
|
-
"ca_key_path": str(self.keys_dir / "ca_key.pem"),
|
368
|
-
"cert_storage_path": str(self.certs_dir),
|
369
|
-
"key_storage_path": str(self.keys_dir),
|
370
|
-
"default_validity_days": 365,
|
371
|
-
"key_size": 2048,
|
372
|
-
"hash_algorithm": "sha256",
|
373
|
-
}
|
374
|
-
try:
|
375
|
-
with open(config_path, "w") as f:
|
376
|
-
json.dump(config, f, indent=2)
|
377
|
-
print(f"✅ Created certificate config: {config_path}")
|
378
|
-
return True
|
379
|
-
except Exception as e:
|
380
|
-
print(f"❌ Failed to create certificate config: {e}")
|
381
|
-
return False
|
382
|
-
|
383
|
-
def validate_certificates(self) -> bool:
|
384
|
-
"""Validate all created certificates."""
|
385
|
-
print("\n🔍 Validating certificates...")
|
386
|
-
cert_files = [
|
387
|
-
"ca_cert.pem",
|
388
|
-
"server_cert.pem",
|
389
|
-
"admin_cert.pem",
|
390
|
-
"user_cert.pem",
|
391
|
-
"readonly_cert.pem",
|
392
|
-
"guest_cert.pem",
|
393
|
-
"proxy_cert.pem",
|
394
|
-
]
|
395
|
-
success = True
|
396
|
-
for cert_file in cert_files:
|
397
|
-
cert_path = self.certs_dir / cert_file
|
398
|
-
if cert_path.exists():
|
399
|
-
try:
|
400
|
-
result = subprocess.run(
|
401
|
-
["openssl", "x509", "-in", str(cert_path), "-text", "-noout"],
|
402
|
-
capture_output=True,
|
403
|
-
text=True,
|
404
|
-
check=True,
|
405
|
-
)
|
406
|
-
print(f"✅ {cert_file}: Valid")
|
407
|
-
except subprocess.CalledProcessError:
|
408
|
-
print(f"❌ {cert_file}: Invalid")
|
409
|
-
success = False
|
410
|
-
else:
|
411
|
-
print(f"⚠️ {cert_file}: Not found")
|
412
|
-
return success
|
413
|
-
|
414
|
-
def generate_all(self) -> bool:
|
415
|
-
"""Generate all certificates."""
|
416
|
-
print("🔐 Generating All Certificates for Security Testing")
|
417
|
-
print("=" * 60)
|
418
|
-
success = True
|
419
|
-
# 1. Create CA certificate
|
420
|
-
if not self.create_ca_certificate():
|
421
|
-
success = False
|
422
|
-
print("❌ Cannot continue without CA certificate")
|
423
|
-
return False
|
424
|
-
# 2. Create server certificate
|
425
|
-
if not self.create_server_certificate():
|
426
|
-
success = False
|
427
|
-
# 3. Create client certificates for different roles
|
428
|
-
print("\n👥 Creating client certificates...")
|
429
|
-
for name, config in self.client_certs.items():
|
430
|
-
if not self.create_client_certificate(name, config):
|
431
|
-
success = False
|
432
|
-
# 4. Create negative test certificates
|
433
|
-
print("\n🚫 Creating negative test certificates...")
|
434
|
-
for name, config in self.negative_certs.items():
|
435
|
-
if not self.create_client_certificate(name, config):
|
436
|
-
success = False
|
437
|
-
# 5. Create legacy certificates for compatibility
|
438
|
-
print("\n🔄 Creating legacy certificates...")
|
439
|
-
if not self.create_legacy_certificates():
|
440
|
-
success = False
|
441
|
-
# 6. Create certificate configuration
|
442
|
-
if not self.create_certificate_config():
|
443
|
-
success = False
|
444
|
-
# 7. Validate certificates
|
445
|
-
if not self.validate_certificates():
|
446
|
-
success = False
|
447
|
-
# Print summary
|
448
|
-
print("\n" + "=" * 60)
|
449
|
-
print("📊 CERTIFICATE GENERATION SUMMARY")
|
450
|
-
print("=" * 60)
|
451
|
-
if success:
|
452
|
-
print("✅ All certificates generated successfully!")
|
453
|
-
print(f"📁 Certificates directory: {self.certs_dir}")
|
454
|
-
print(f"🔑 Keys directory: {self.keys_dir}")
|
455
|
-
print("\n📋 Generated certificates:")
|
456
|
-
cert_files = list(self.certs_dir.glob("*.pem")) + list(
|
457
|
-
self.certs_dir.glob("*.crt")
|
458
|
-
)
|
459
|
-
for cert_file in sorted(cert_files):
|
460
|
-
print(f" - {cert_file.name}")
|
461
|
-
key_files = list(self.keys_dir.glob("*.pem")) + list(
|
462
|
-
self.keys_dir.glob("*.key")
|
463
|
-
)
|
464
|
-
for key_file in sorted(key_files):
|
465
|
-
print(f" - {key_file.name}")
|
466
|
-
else:
|
467
|
-
print("❌ Some certificates failed to generate")
|
468
|
-
print("Check the error messages above")
|
469
|
-
return success
|
470
|
-
|
471
|
-
|
472
|
-
def main():
|
473
|
-
"""Main function."""
|
474
|
-
generator = CertificateGenerator()
|
475
|
-
try:
|
476
|
-
success = generator.generate_all()
|
477
|
-
sys.exit(0 if success else 1)
|
478
|
-
except KeyboardInterrupt:
|
479
|
-
print("\n⚠️ Certificate generation interrupted by user")
|
480
|
-
sys.exit(1)
|
481
|
-
except Exception as e:
|
482
|
-
print(f"\n❌ Certificate generation failed: {e}")
|
483
|
-
sys.exit(1)
|
484
|
-
|
485
|
-
|
486
|
-
if __name__ == "__main__":
|
487
|
-
main()
|