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.
- mcp_proxy_adapter/__main__.py +12 -0
- mcp_proxy_adapter/api/app.py +138 -11
- mcp_proxy_adapter/api/handlers.py +16 -1
- mcp_proxy_adapter/api/middleware/__init__.py +30 -29
- mcp_proxy_adapter/api/middleware/auth_adapter.py +235 -0
- mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
- mcp_proxy_adapter/api/middleware/factory.py +219 -0
- mcp_proxy_adapter/api/middleware/logging.py +32 -6
- mcp_proxy_adapter/api/middleware/mtls_adapter.py +305 -0
- mcp_proxy_adapter/api/middleware/mtls_middleware.py +296 -0
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +135 -0
- mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +241 -0
- mcp_proxy_adapter/api/middleware/roles_adapter.py +365 -0
- mcp_proxy_adapter/api/middleware/roles_middleware.py +381 -0
- mcp_proxy_adapter/api/middleware/security.py +376 -0
- mcp_proxy_adapter/api/middleware/token_auth_middleware.py +261 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- mcp_proxy_adapter/commands/__init__.py +13 -4
- mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
- mcp_proxy_adapter/commands/base.py +61 -30
- mcp_proxy_adapter/commands/builtin_commands.py +89 -0
- mcp_proxy_adapter/commands/catalog_manager.py +838 -0
- mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
- mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
- mcp_proxy_adapter/commands/command_registry.py +705 -345
- mcp_proxy_adapter/commands/dependency_manager.py +245 -0
- mcp_proxy_adapter/commands/health_command.py +7 -0
- mcp_proxy_adapter/commands/hooks.py +200 -167
- mcp_proxy_adapter/commands/key_management_command.py +506 -0
- mcp_proxy_adapter/commands/load_command.py +176 -0
- mcp_proxy_adapter/commands/plugins_command.py +235 -0
- mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
- mcp_proxy_adapter/commands/proxy_registration_command.py +268 -0
- mcp_proxy_adapter/commands/reload_command.py +48 -50
- mcp_proxy_adapter/commands/result.py +1 -0
- mcp_proxy_adapter/commands/roles_management_command.py +697 -0
- mcp_proxy_adapter/commands/ssl_setup_command.py +483 -0
- mcp_proxy_adapter/commands/token_management_command.py +529 -0
- mcp_proxy_adapter/commands/transport_management_command.py +144 -0
- mcp_proxy_adapter/commands/unload_command.py +158 -0
- mcp_proxy_adapter/config.py +99 -2
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +827 -0
- mcp_proxy_adapter/core/config_converter.py +405 -0
- mcp_proxy_adapter/core/config_validator.py +218 -0
- mcp_proxy_adapter/core/logging.py +11 -0
- mcp_proxy_adapter/core/protocol_manager.py +226 -0
- mcp_proxy_adapter/core/proxy_registration.py +270 -0
- mcp_proxy_adapter/core/role_utils.py +426 -0
- mcp_proxy_adapter/core/security_adapter.py +373 -0
- mcp_proxy_adapter/core/security_factory.py +239 -0
- mcp_proxy_adapter/core/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +233 -0
- mcp_proxy_adapter/core/transport_manager.py +292 -0
- mcp_proxy_adapter/custom_openapi.py +22 -11
- mcp_proxy_adapter/examples/basic_server/config.json +58 -23
- mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +54 -0
- mcp_proxy_adapter/examples/basic_server/config_http.json +70 -0
- mcp_proxy_adapter/examples/basic_server/config_http_only.json +52 -0
- mcp_proxy_adapter/examples/basic_server/config_https.json +58 -0
- mcp_proxy_adapter/examples/basic_server/config_mtls.json +58 -0
- mcp_proxy_adapter/examples/basic_server/config_ssl.json +46 -0
- mcp_proxy_adapter/examples/basic_server/server.py +17 -1
- mcp_proxy_adapter/examples/custom_commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +339 -23
- mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +105 -0
- mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +129 -0
- mcp_proxy_adapter/examples/custom_commands/config.json +97 -41
- mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_https_only.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +46 -0
- mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +33 -0
- mcp_proxy_adapter/examples/custom_commands/full_help_response.json +1 -0
- mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +629 -0
- mcp_proxy_adapter/examples/custom_commands/get_openapi.py +103 -0
- mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +129 -0
- mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +278 -0
- mcp_proxy_adapter/examples/custom_commands/server.py +92 -63
- mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +75 -0
- mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +299 -0
- mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +278 -0
- mcp_proxy_adapter/examples/custom_commands/test_openapi.py +27 -0
- mcp_proxy_adapter/examples/custom_commands/test_registry.py +23 -0
- mcp_proxy_adapter/examples/custom_commands/test_simple.py +19 -0
- mcp_proxy_adapter/examples/custom_project_example/README.md +103 -0
- mcp_proxy_adapter/examples/custom_project_example/README_EN.md +103 -0
- mcp_proxy_adapter/examples/simple_custom_commands/README.md +149 -0
- mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +149 -0
- mcp_proxy_adapter/main.py +175 -0
- mcp_proxy_adapter/schemas/roles_schema.json +162 -0
- mcp_proxy_adapter/tests/unit/test_config.py +53 -0
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.0.0.dist-info/RECORD +179 -0
- mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
- mcp_proxy_adapter-4.1.0.dist-info/RECORD +0 -110
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.0.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
)
|