mcp-proxy-adapter 4.1.0__py3-none-any.whl → 6.0.0__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.
Files changed (101) hide show
  1. mcp_proxy_adapter/__main__.py +12 -0
  2. mcp_proxy_adapter/api/app.py +138 -11
  3. mcp_proxy_adapter/api/handlers.py +16 -1
  4. mcp_proxy_adapter/api/middleware/__init__.py +30 -29
  5. mcp_proxy_adapter/api/middleware/auth_adapter.py +235 -0
  6. mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
  7. mcp_proxy_adapter/api/middleware/factory.py +219 -0
  8. mcp_proxy_adapter/api/middleware/logging.py +32 -6
  9. mcp_proxy_adapter/api/middleware/mtls_adapter.py +305 -0
  10. mcp_proxy_adapter/api/middleware/mtls_middleware.py +296 -0
  11. mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
  12. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +241 -0
  13. mcp_proxy_adapter/api/middleware/roles_adapter.py +365 -0
  14. mcp_proxy_adapter/api/middleware/roles_middleware.py +381 -0
  15. mcp_proxy_adapter/api/middleware/security.py +376 -0
  16. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +261 -0
  17. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  18. mcp_proxy_adapter/commands/__init__.py +13 -4
  19. mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
  20. mcp_proxy_adapter/commands/base.py +61 -30
  21. mcp_proxy_adapter/commands/builtin_commands.py +89 -0
  22. mcp_proxy_adapter/commands/catalog_manager.py +838 -0
  23. mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
  24. mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
  25. mcp_proxy_adapter/commands/command_registry.py +705 -345
  26. mcp_proxy_adapter/commands/dependency_manager.py +245 -0
  27. mcp_proxy_adapter/commands/health_command.py +7 -0
  28. mcp_proxy_adapter/commands/hooks.py +200 -167
  29. mcp_proxy_adapter/commands/key_management_command.py +506 -0
  30. mcp_proxy_adapter/commands/load_command.py +176 -0
  31. mcp_proxy_adapter/commands/plugins_command.py +235 -0
  32. mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
  33. mcp_proxy_adapter/commands/proxy_registration_command.py +268 -0
  34. mcp_proxy_adapter/commands/reload_command.py +48 -50
  35. mcp_proxy_adapter/commands/result.py +1 -0
  36. mcp_proxy_adapter/commands/roles_management_command.py +697 -0
  37. mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
  38. mcp_proxy_adapter/commands/token_management_command.py +529 -0
  39. mcp_proxy_adapter/commands/transport_management_command.py +144 -0
  40. mcp_proxy_adapter/commands/unload_command.py +158 -0
  41. mcp_proxy_adapter/config.py +99 -2
  42. mcp_proxy_adapter/core/auth_validator.py +606 -0
  43. mcp_proxy_adapter/core/certificate_utils.py +827 -0
  44. mcp_proxy_adapter/core/config_converter.py +405 -0
  45. mcp_proxy_adapter/core/config_validator.py +218 -0
  46. mcp_proxy_adapter/core/logging.py +11 -0
  47. mcp_proxy_adapter/core/protocol_manager.py +226 -0
  48. mcp_proxy_adapter/core/proxy_registration.py +270 -0
  49. mcp_proxy_adapter/core/role_utils.py +426 -0
  50. mcp_proxy_adapter/core/security_adapter.py +373 -0
  51. mcp_proxy_adapter/core/security_factory.py +239 -0
  52. mcp_proxy_adapter/core/settings.py +1 -0
  53. mcp_proxy_adapter/core/ssl_utils.py +233 -0
  54. mcp_proxy_adapter/core/transport_manager.py +292 -0
  55. mcp_proxy_adapter/custom_openapi.py +22 -11
  56. mcp_proxy_adapter/examples/basic_server/config.json +58 -23
  57. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +54 -0
  58. mcp_proxy_adapter/examples/basic_server/config_http.json +70 -0
  59. mcp_proxy_adapter/examples/basic_server/config_http_only.json +52 -0
  60. mcp_proxy_adapter/examples/basic_server/config_https.json +58 -0
  61. mcp_proxy_adapter/examples/basic_server/config_mtls.json +58 -0
  62. mcp_proxy_adapter/examples/basic_server/config_ssl.json +46 -0
  63. mcp_proxy_adapter/examples/basic_server/server.py +17 -1
  64. mcp_proxy_adapter/examples/custom_commands/__init__.py +1 -1
  65. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +339 -23
  66. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +105 -0
  67. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +129 -0
  68. mcp_proxy_adapter/examples/custom_commands/config.json +97 -41
  69. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +46 -0
  70. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +46 -0
  71. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +33 -0
  72. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +46 -0
  73. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +33 -0
  74. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +33 -0
  75. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +1 -0
  76. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +629 -0
  77. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +103 -0
  78. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +129 -0
  79. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +278 -0
  80. mcp_proxy_adapter/examples/custom_commands/server.py +92 -63
  81. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +75 -0
  82. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +299 -0
  83. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +278 -0
  84. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +27 -0
  85. mcp_proxy_adapter/examples/custom_commands/test_registry.py +23 -0
  86. mcp_proxy_adapter/examples/custom_commands/test_simple.py +19 -0
  87. mcp_proxy_adapter/examples/custom_project_example/README.md +103 -0
  88. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +103 -0
  89. mcp_proxy_adapter/examples/simple_custom_commands/README.md +149 -0
  90. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +149 -0
  91. mcp_proxy_adapter/main.py +175 -0
  92. mcp_proxy_adapter/schemas/roles_schema.json +162 -0
  93. mcp_proxy_adapter/tests/unit/test_config.py +53 -0
  94. mcp_proxy_adapter/version.py +1 -1
  95. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/METADATA +2 -1
  96. mcp_proxy_adapter-6.0.0.dist-info/RECORD +179 -0
  97. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  98. mcp_proxy_adapter-4.1.0.dist-info/RECORD +0 -110
  99. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/WHEEL +0 -0
  100. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/licenses/LICENSE +0 -0
  101. {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,608 @@
1
+ """
2
+ Certificate Management Command
3
+
4
+ This module provides commands for certificate management including creation,
5
+ validation, revocation, and information retrieval.
6
+
7
+ Author: MCP Proxy Adapter Team
8
+ Version: 1.0.0
9
+ """
10
+
11
+ import logging
12
+ import os
13
+ from typing import Dict, List, Optional, Any
14
+ from pathlib import Path
15
+
16
+ from .base import Command
17
+ from .result import CommandResult, SuccessResult, ErrorResult
18
+ from ..core.certificate_utils import CertificateUtils
19
+ from ..core.auth_validator import AuthValidator
20
+ from ..core.role_utils import RoleUtils
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class CertificateResult:
26
+ """
27
+ Result class for certificate operations.
28
+
29
+ Contains certificate information and operation status.
30
+ """
31
+
32
+ def __init__(self, cert_path: str, cert_type: str, common_name: str,
33
+ roles: Optional[List[str]] = None, expiry_date: Optional[str] = None,
34
+ serial_number: Optional[str] = None, status: str = "valid",
35
+ error: Optional[str] = None):
36
+ """
37
+ Initialize certificate result.
38
+
39
+ Args:
40
+ cert_path: Path to certificate file
41
+ cert_type: Type of certificate (CA, server, client)
42
+ common_name: Common name of the certificate
43
+ roles: List of roles assigned to certificate
44
+ expiry_date: Certificate expiry date
45
+ serial_number: Certificate serial number
46
+ status: Certificate status (valid, expired, revoked, error)
47
+ error: Error message if any
48
+ """
49
+ self.cert_path = cert_path
50
+ self.cert_type = cert_type
51
+ self.common_name = common_name
52
+ self.roles = roles or []
53
+ self.expiry_date = expiry_date
54
+ self.serial_number = serial_number
55
+ self.status = status
56
+ self.error = error
57
+
58
+ def to_dict(self) -> Dict[str, Any]:
59
+ """
60
+ Convert to dictionary format.
61
+
62
+ Returns:
63
+ Dictionary representation
64
+ """
65
+ return {
66
+ "cert_path": self.cert_path,
67
+ "cert_type": self.cert_type,
68
+ "common_name": self.common_name,
69
+ "roles": self.roles,
70
+ "expiry_date": self.expiry_date,
71
+ "serial_number": self.serial_number,
72
+ "status": self.status,
73
+ "error": self.error
74
+ }
75
+
76
+ def get_schema(self) -> Dict[str, Any]:
77
+ """
78
+ Get JSON schema for this result.
79
+
80
+ Returns:
81
+ JSON schema dictionary
82
+ """
83
+ return {
84
+ "type": "object",
85
+ "properties": {
86
+ "cert_path": {"type": "string", "description": "Path to certificate file"},
87
+ "cert_type": {"type": "string", "enum": ["CA", "server", "client"],
88
+ "description": "Type of certificate"},
89
+ "common_name": {"type": "string", "description": "Common name of certificate"},
90
+ "roles": {"type": "array", "items": {"type": "string"},
91
+ "description": "List of roles assigned to certificate"},
92
+ "expiry_date": {"type": "string", "description": "Certificate expiry date"},
93
+ "serial_number": {"type": "string", "description": "Certificate serial number"},
94
+ "status": {"type": "string", "enum": ["valid", "expired", "revoked", "error"],
95
+ "description": "Certificate status"},
96
+ "error": {"type": "string", "description": "Error message if any"}
97
+ },
98
+ "required": ["cert_path", "cert_type", "common_name", "status"]
99
+ }
100
+
101
+
102
+ class CertificateManagementCommand(Command):
103
+ """
104
+ Command for certificate management.
105
+
106
+ Provides methods for creating, managing, and validating certificates.
107
+ """
108
+
109
+ # Command metadata
110
+ name = "certificate_management"
111
+ version = "1.0.0"
112
+ descr = "Certificate creation, validation, and management"
113
+ category = "security"
114
+ author = "MCP Proxy Adapter Team"
115
+ email = "team@mcp-proxy-adapter.com"
116
+ source_url = "https://github.com/mcp-proxy-adapter"
117
+ result_class = CertificateResult
118
+
119
+ def __init__(self):
120
+ """Initialize certificate management command."""
121
+ super().__init__()
122
+ self.certificate_utils = CertificateUtils()
123
+ self.auth_validator = AuthValidator()
124
+ self.role_utils = RoleUtils()
125
+
126
+ async def execute(self, **kwargs) -> CommandResult:
127
+ """
128
+ Execute certificate management command.
129
+
130
+ Args:
131
+ **kwargs: Command parameters including:
132
+ - action: Action to perform (cert_create_ca, cert_create_server, cert_create_client, cert_revoke, cert_list, cert_info)
133
+ - common_name: Common name for certificate creation
134
+ - roles: List of roles for certificate creation
135
+ - ca_cert_path: CA certificate path for server/client certificate creation
136
+ - ca_key_path: CA key path for server/client certificate creation
137
+ - output_dir: Output directory for certificate creation
138
+ - validity_days: Certificate validity period in days
139
+ - key_size: Key size in bits for CA certificate creation
140
+ - cert_path: Certificate path for revocation and info
141
+ - cert_dir: Directory for certificate listing
142
+
143
+ Returns:
144
+ CommandResult with certificate operation status
145
+ """
146
+ action = kwargs.get("action", "cert_list")
147
+
148
+ if action == "cert_create_ca":
149
+ common_name = kwargs.get("common_name")
150
+ output_dir = kwargs.get("output_dir")
151
+ validity_days = kwargs.get("validity_days", 365)
152
+ key_size = kwargs.get("key_size", 2048)
153
+ return await self.cert_create_ca(common_name, output_dir, validity_days, key_size)
154
+ elif action == "cert_create_server":
155
+ common_name = kwargs.get("common_name")
156
+ roles = kwargs.get("roles", [])
157
+ ca_cert_path = kwargs.get("ca_cert_path")
158
+ ca_key_path = kwargs.get("ca_key_path")
159
+ output_dir = kwargs.get("output_dir")
160
+ validity_days = kwargs.get("validity_days", 365)
161
+ return await self.cert_create_server(common_name, roles, ca_cert_path, ca_key_path, output_dir, validity_days)
162
+ elif action == "cert_create_client":
163
+ common_name = kwargs.get("common_name")
164
+ roles = kwargs.get("roles", [])
165
+ ca_cert_path = kwargs.get("ca_cert_path")
166
+ ca_key_path = kwargs.get("ca_key_path")
167
+ output_dir = kwargs.get("output_dir")
168
+ validity_days = kwargs.get("validity_days", 365)
169
+ return await self.cert_create_client(common_name, roles, ca_cert_path, ca_key_path, output_dir, validity_days)
170
+ elif action == "cert_revoke":
171
+ cert_path = kwargs.get("cert_path")
172
+ return await self.cert_revoke(cert_path)
173
+ elif action == "cert_list":
174
+ cert_dir = kwargs.get("cert_dir", "/tmp")
175
+ return await self.cert_list(cert_dir)
176
+ elif action == "cert_info":
177
+ cert_path = kwargs.get("cert_path")
178
+ return await self.cert_info(cert_path)
179
+ else:
180
+ return ErrorResult(
181
+ message=f"Unknown action: {action}. Supported actions: cert_create_ca, cert_create_server, cert_create_client, cert_revoke, cert_list, cert_info"
182
+ )
183
+
184
+ async def cert_create_ca(self, common_name: str, output_dir: str,
185
+ validity_days: int = 365, key_size: int = 2048) -> CommandResult:
186
+ """
187
+ Create a CA certificate and private key.
188
+
189
+ Args:
190
+ common_name: Common name for the CA certificate
191
+ output_dir: Directory to save certificate and key files
192
+ validity_days: Certificate validity period in days
193
+ key_size: RSA key size in bits
194
+
195
+ Returns:
196
+ CommandResult with CA certificate creation status
197
+ """
198
+ try:
199
+ logger.info(f"Creating CA certificate: {common_name}")
200
+
201
+ # Validate parameters
202
+ if not common_name or not common_name.strip():
203
+ return ErrorResult(
204
+ message="Common name cannot be empty"
205
+ )
206
+
207
+ if validity_days <= 0:
208
+ return ErrorResult(
209
+ message="Validity days must be positive"
210
+ )
211
+
212
+ if key_size < 1024:
213
+ return ErrorResult(
214
+ message="Key size must be at least 1024 bits"
215
+ )
216
+
217
+ # Create CA certificate
218
+ result = self.certificate_utils.create_ca_certificate(
219
+ common_name, output_dir, validity_days, key_size
220
+ )
221
+
222
+ # Validate created certificate (CA certificates don't need server validation)
223
+ cert_path = result.get("cert_path")
224
+ if cert_path and os.path.exists(cert_path):
225
+ # For CA certificates, we only check if the file exists and is readable
226
+ try:
227
+ with open(cert_path, 'rb') as f:
228
+ cert_data = f.read()
229
+ if not cert_data:
230
+ return ErrorResult(
231
+ message="Created CA certificate file is empty"
232
+ )
233
+ except Exception as e:
234
+ return ErrorResult(
235
+ message=f"Created CA certificate file is not readable: {str(e)}"
236
+ )
237
+
238
+ cert_result = CertificateResult(
239
+ cert_path=result.get("cert_path", ""),
240
+ cert_type="CA",
241
+ common_name=common_name,
242
+ status="valid"
243
+ )
244
+
245
+ logger.info(f"CA certificate created successfully: {result.get('cert_path')}")
246
+ return SuccessResult(
247
+ data={
248
+ "certificate": cert_result.to_dict(),
249
+ "files": result
250
+ }
251
+ )
252
+
253
+ except Exception as e:
254
+ logger.error(f"CA certificate creation failed: {e}")
255
+ return ErrorResult(
256
+ message=f"CA certificate creation failed: {str(e)}"
257
+ )
258
+
259
+ async def cert_create_server(self, common_name: str, roles: List[str],
260
+ ca_cert_path: str, ca_key_path: str,
261
+ output_dir: str, validity_days: int = 365) -> CommandResult:
262
+ """
263
+ Create a server certificate signed by CA.
264
+
265
+ Args:
266
+ common_name: Common name for the server certificate
267
+ roles: List of roles to assign to the certificate
268
+ ca_cert_path: Path to CA certificate file
269
+ ca_key_path: Path to CA private key file
270
+ output_dir: Directory to save certificate and key files
271
+ validity_days: Certificate validity period in days
272
+
273
+ Returns:
274
+ CommandResult with server certificate creation status
275
+ """
276
+ try:
277
+ logger.info(f"Creating server certificate: {common_name}")
278
+
279
+ # Validate parameters
280
+ if not common_name or not common_name.strip():
281
+ return ErrorResult(
282
+ message="Common name cannot be empty"
283
+ )
284
+
285
+ if not roles:
286
+ return ErrorResult(
287
+ message="At least one role must be specified"
288
+ )
289
+
290
+ # Validate roles
291
+ if not self.role_utils.validate_roles(roles):
292
+ return ErrorResult(
293
+ message="Invalid roles specified"
294
+ )
295
+
296
+ # Check CA files
297
+ if not os.path.exists(ca_cert_path):
298
+ return ErrorResult(
299
+ message=f"CA certificate not found: {ca_cert_path}"
300
+ )
301
+
302
+ if not os.path.exists(ca_key_path):
303
+ return ErrorResult(
304
+ message=f"CA private key not found: {ca_key_path}"
305
+ )
306
+
307
+ # Create server certificate
308
+ result = self.certificate_utils.create_server_certificate(
309
+ common_name, roles, ca_cert_path, ca_key_path, output_dir, validity_days
310
+ )
311
+
312
+ # Validate created certificate
313
+ cert_path = result.get("cert_path")
314
+ if cert_path and os.path.exists(cert_path):
315
+ validation = self.auth_validator.validate_certificate(cert_path)
316
+ if not validation.is_valid:
317
+ return ErrorResult(
318
+ message=f"Created server certificate validation failed: {validation.error_message}"
319
+ )
320
+
321
+ cert_result = CertificateResult(
322
+ cert_path=result.get("cert_path", ""),
323
+ cert_type="server",
324
+ common_name=common_name,
325
+ roles=roles,
326
+ status="valid"
327
+ )
328
+
329
+ logger.info(f"Server certificate created successfully: {result.get('cert_path')}")
330
+ return SuccessResult(
331
+ data={
332
+ "certificate": cert_result.to_dict(),
333
+ "files": result
334
+ }
335
+ )
336
+
337
+ except Exception as e:
338
+ logger.error(f"Server certificate creation failed: {e}")
339
+ return ErrorResult(
340
+ message=f"Server certificate creation failed: {str(e)}"
341
+ )
342
+
343
+ async def cert_create_client(self, common_name: str, roles: List[str],
344
+ ca_cert_path: str, ca_key_path: str,
345
+ output_dir: str, validity_days: int = 365) -> CommandResult:
346
+ """
347
+ Create a client certificate signed by CA.
348
+
349
+ Args:
350
+ common_name: Common name for the client certificate
351
+ roles: List of roles to assign to the certificate
352
+ ca_cert_path: Path to CA certificate file
353
+ ca_key_path: Path to CA private key file
354
+ output_dir: Directory to save certificate and key files
355
+ validity_days: Certificate validity period in days
356
+
357
+ Returns:
358
+ CommandResult with client certificate creation status
359
+ """
360
+ try:
361
+ logger.info(f"Creating client certificate: {common_name}")
362
+
363
+ # Validate parameters
364
+ if not common_name or not common_name.strip():
365
+ return ErrorResult(
366
+ message="Common name cannot be empty"
367
+ )
368
+
369
+ if not roles:
370
+ return ErrorResult(
371
+ message="At least one role must be specified"
372
+ )
373
+
374
+ # Validate roles
375
+ if not self.role_utils.validate_roles(roles):
376
+ return ErrorResult(
377
+ message="Invalid roles specified"
378
+ )
379
+
380
+ # Check CA files
381
+ if not os.path.exists(ca_cert_path):
382
+ return ErrorResult(
383
+ message=f"CA certificate not found: {ca_cert_path}"
384
+ )
385
+
386
+ if not os.path.exists(ca_key_path):
387
+ return ErrorResult(
388
+ message=f"CA private key not found: {ca_key_path}"
389
+ )
390
+
391
+ # Create client certificate
392
+ result = self.certificate_utils.create_client_certificate(
393
+ common_name, roles, ca_cert_path, ca_key_path, output_dir, validity_days
394
+ )
395
+
396
+ # Validate created certificate
397
+ cert_path = result.get("cert_path")
398
+ if cert_path and os.path.exists(cert_path):
399
+ validation = self.auth_validator.validate_certificate(cert_path)
400
+ if not validation.is_valid:
401
+ return ErrorResult(
402
+ message=f"Created client certificate validation failed: {validation.error_message}"
403
+ )
404
+
405
+ cert_result = CertificateResult(
406
+ cert_path=result.get("cert_path", ""),
407
+ cert_type="client",
408
+ common_name=common_name,
409
+ roles=roles,
410
+ status="valid"
411
+ )
412
+
413
+ logger.info(f"Client certificate created successfully: {result.get('cert_path')}")
414
+ return SuccessResult(
415
+ data={
416
+ "certificate": cert_result.to_dict(),
417
+ "files": result
418
+ }
419
+ )
420
+
421
+ except Exception as e:
422
+ logger.error(f"Client certificate creation failed: {e}")
423
+ return ErrorResult(
424
+ message=f"Client certificate creation failed: {str(e)}"
425
+ )
426
+
427
+ async def cert_revoke(self, cert_path: str) -> CommandResult:
428
+ """
429
+ Revoke a certificate.
430
+
431
+ Args:
432
+ cert_path: Path to certificate file to revoke
433
+
434
+ Returns:
435
+ CommandResult with revocation status
436
+ """
437
+ try:
438
+ logger.info(f"Revoking certificate: {cert_path}")
439
+
440
+ # Validate parameters
441
+ if not cert_path or not os.path.exists(cert_path):
442
+ return ErrorResult(
443
+ message=f"Certificate file not found: {cert_path}"
444
+ )
445
+
446
+ # Get certificate info before revocation
447
+ cert_info = self.certificate_utils.get_certificate_info(cert_path)
448
+ if not cert_info:
449
+ return ErrorResult(
450
+ message="Could not read certificate information"
451
+ )
452
+
453
+ # Revoke certificate
454
+ result = self.certificate_utils.revoke_certificate(cert_path)
455
+
456
+ cert_result = CertificateResult(
457
+ cert_path=cert_path,
458
+ cert_type=cert_info.get("type", "unknown"),
459
+ common_name=cert_info.get("common_name", ""),
460
+ roles=cert_info.get("roles", []),
461
+ serial_number=cert_info.get("serial_number"),
462
+ status="revoked"
463
+ )
464
+
465
+ logger.info(f"Certificate revoked successfully: {cert_path}")
466
+ return SuccessResult(
467
+ data={
468
+ "certificate": cert_result.to_dict(),
469
+ "revocation_result": result
470
+ }
471
+ )
472
+
473
+ except Exception as e:
474
+ logger.error(f"Certificate revocation failed: {e}")
475
+ return ErrorResult(
476
+ message=f"Certificate revocation failed: {str(e)}"
477
+ )
478
+
479
+ async def cert_list(self, cert_dir: str) -> CommandResult:
480
+ """
481
+ List all certificates in a directory.
482
+
483
+ Args:
484
+ cert_dir: Directory to scan for certificates
485
+
486
+ Returns:
487
+ CommandResult with list of certificates
488
+ """
489
+ try:
490
+ logger.info(f"Listing certificates in directory: {cert_dir}")
491
+
492
+ # Validate parameters
493
+ if not cert_dir or not os.path.exists(cert_dir):
494
+ return ErrorResult(
495
+ message=f"Directory not found: {cert_dir}"
496
+ )
497
+
498
+ if not os.path.isdir(cert_dir):
499
+ return ErrorResult(
500
+ message=f"Path is not a directory: {cert_dir}"
501
+ )
502
+
503
+ # List certificates
504
+ certificates = []
505
+ cert_extensions = ['.crt', '.pem', '.cer', '.der']
506
+
507
+ for file_path in Path(cert_dir).rglob('*'):
508
+ if file_path.is_file() and file_path.suffix.lower() in cert_extensions:
509
+ try:
510
+ cert_info = self.certificate_utils.get_certificate_info(str(file_path))
511
+ if cert_info:
512
+ cert_result = CertificateResult(
513
+ cert_path=str(file_path),
514
+ cert_type=cert_info.get("type", "unknown"),
515
+ common_name=cert_info.get("common_name", ""),
516
+ roles=cert_info.get("roles", []),
517
+ expiry_date=cert_info.get("expiry_date"),
518
+ serial_number=cert_info.get("serial_number"),
519
+ status=cert_info.get("status", "valid")
520
+ )
521
+ certificates.append(cert_result.to_dict())
522
+ except Exception as e:
523
+ logger.warning(f"Could not read certificate {file_path}: {e}")
524
+ # Add certificate with error status
525
+ cert_result = CertificateResult(
526
+ cert_path=str(file_path),
527
+ cert_type="unknown",
528
+ common_name="",
529
+ status="error",
530
+ error=str(e)
531
+ )
532
+ certificates.append(cert_result.to_dict())
533
+
534
+ logger.info(f"Found {len(certificates)} certificates in {cert_dir}")
535
+ return SuccessResult(
536
+ data={
537
+ "certificates": certificates,
538
+ "total_count": len(certificates),
539
+ "directory": cert_dir
540
+ }
541
+ )
542
+
543
+ except Exception as e:
544
+ logger.error(f"Certificate listing failed: {e}")
545
+ return ErrorResult(
546
+ message=f"Certificate listing failed: {str(e)}"
547
+ )
548
+
549
+ async def cert_info(self, cert_path: str) -> CommandResult:
550
+ """
551
+ Get detailed information about a certificate.
552
+
553
+ Args:
554
+ cert_path: Path to certificate file
555
+
556
+ Returns:
557
+ CommandResult with certificate information
558
+ """
559
+ try:
560
+ logger.info(f"Getting certificate info: {cert_path}")
561
+
562
+ # Validate parameters
563
+ if not cert_path or not os.path.exists(cert_path):
564
+ return ErrorResult(
565
+ message=f"Certificate file not found: {cert_path}"
566
+ )
567
+
568
+ # Get certificate information
569
+ cert_info = self.certificate_utils.get_certificate_info(cert_path)
570
+ if not cert_info:
571
+ return ErrorResult(
572
+ message="Could not read certificate information"
573
+ )
574
+
575
+ # Validate certificate
576
+ validation = self.auth_validator.validate_certificate(cert_path)
577
+ status = "valid" if validation.is_valid else "error"
578
+
579
+ cert_result = CertificateResult(
580
+ cert_path=cert_path,
581
+ cert_type=cert_info.get("type", "unknown"),
582
+ common_name=cert_info.get("common_name", ""),
583
+ roles=cert_info.get("roles", []),
584
+ expiry_date=cert_info.get("expiry_date"),
585
+ serial_number=cert_info.get("serial_number"),
586
+ status=status,
587
+ error=None if validation.is_valid else validation.error_message
588
+ )
589
+
590
+ logger.info(f"Certificate info retrieved successfully: {cert_path}")
591
+ return SuccessResult(
592
+ data={
593
+ "certificate": cert_result.to_dict(),
594
+ "validation": {
595
+ "is_valid": validation.is_valid,
596
+ "error_code": validation.error_code,
597
+ "error_message": validation.error_message,
598
+ "roles": validation.roles
599
+ },
600
+ "details": cert_info
601
+ }
602
+ )
603
+
604
+ except Exception as e:
605
+ logger.error(f"Certificate info retrieval failed: {e}")
606
+ return ErrorResult(
607
+ message=f"Certificate info retrieval failed: {str(e)}"
608
+ )