mcp-proxy-adapter 4.1.1__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 +703 -354
- 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 +12 -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 +101 -18
- 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 -68
- 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.1.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.1.dist-info/RECORD +0 -110
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,606 @@
|
|
1
|
+
"""
|
2
|
+
Universal Authentication Validator
|
3
|
+
|
4
|
+
This module provides a universal authentication validator that supports
|
5
|
+
certificate, token, mTLS, and SSL validation with JSON-RPC error codes.
|
6
|
+
|
7
|
+
Author: MCP Proxy Adapter Team
|
8
|
+
Version: 1.0.0
|
9
|
+
"""
|
10
|
+
|
11
|
+
import json
|
12
|
+
import logging
|
13
|
+
import os
|
14
|
+
from datetime import datetime
|
15
|
+
from typing import Dict, List, Optional, Any, Union
|
16
|
+
from pathlib import Path
|
17
|
+
|
18
|
+
from cryptography import x509
|
19
|
+
from cryptography.hazmat.primitives import hashes
|
20
|
+
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
21
|
+
|
22
|
+
|
23
|
+
# Standard JSON-RPC error codes
|
24
|
+
JSON_RPC_ERRORS = {
|
25
|
+
# General errors
|
26
|
+
-32600: "Invalid Request", # Invalid request
|
27
|
+
-32601: "Method not found", # Method not found
|
28
|
+
-32602: "Invalid params", # Invalid parameters
|
29
|
+
-32603: "Internal error", # Internal error
|
30
|
+
-32700: "Parse error", # Parse error
|
31
|
+
|
32
|
+
# Custom codes for authentication
|
33
|
+
-32001: "Authentication disabled", # Authentication disabled
|
34
|
+
-32002: "Invalid configuration", # Invalid configuration
|
35
|
+
-32003: "Certificate validation failed", # Certificate validation failed
|
36
|
+
-32004: "Token validation failed", # Token validation failed
|
37
|
+
-32005: "MTLS validation failed", # MTLS validation failed
|
38
|
+
-32006: "SSL validation failed", # SSL validation failed
|
39
|
+
-32007: "Role validation failed", # Role validation failed
|
40
|
+
-32008: "Certificate expired", # Certificate expired
|
41
|
+
-32009: "Certificate not found", # Certificate not found
|
42
|
+
-32010: "Token expired", # Token expired
|
43
|
+
-32011: "Token not found", # Token not found
|
44
|
+
}
|
45
|
+
|
46
|
+
|
47
|
+
class AuthValidationResult:
|
48
|
+
"""
|
49
|
+
Authentication validation result.
|
50
|
+
|
51
|
+
Contains validation status, error information, and extracted roles.
|
52
|
+
"""
|
53
|
+
|
54
|
+
def __init__(self, is_valid: bool, error_code: Optional[int] = None,
|
55
|
+
error_message: Optional[str] = None, roles: Optional[List[str]] = None):
|
56
|
+
"""
|
57
|
+
Initialize authentication validation result.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
is_valid: Whether authentication is valid
|
61
|
+
error_code: JSON-RPC error code if validation failed
|
62
|
+
error_message: Error message if validation failed
|
63
|
+
roles: List of roles extracted from authentication data
|
64
|
+
"""
|
65
|
+
self.is_valid = is_valid
|
66
|
+
self.error_code = error_code
|
67
|
+
self.error_message = error_message
|
68
|
+
self.roles = roles or []
|
69
|
+
|
70
|
+
def to_json_rpc_error(self) -> Dict[str, Any]:
|
71
|
+
"""
|
72
|
+
Convert to JSON-RPC error format.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
JSON-RPC error dictionary
|
76
|
+
"""
|
77
|
+
if self.is_valid:
|
78
|
+
return {}
|
79
|
+
|
80
|
+
return {
|
81
|
+
"code": self.error_code or -32603,
|
82
|
+
"message": self.error_message or JSON_RPC_ERRORS.get(self.error_code, "Unknown error"),
|
83
|
+
"data": {
|
84
|
+
"validation_type": "authentication",
|
85
|
+
"roles": self.roles
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
def to_dict(self) -> Dict[str, Any]:
|
90
|
+
"""
|
91
|
+
Convert to dictionary format.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
Dictionary representation
|
95
|
+
"""
|
96
|
+
return {
|
97
|
+
"is_valid": self.is_valid,
|
98
|
+
"error_code": self.error_code,
|
99
|
+
"error_message": self.error_message,
|
100
|
+
"roles": self.roles
|
101
|
+
}
|
102
|
+
|
103
|
+
|
104
|
+
class AuthValidator:
|
105
|
+
"""
|
106
|
+
Universal authentication validator.
|
107
|
+
|
108
|
+
Provides methods to validate different types of authentication:
|
109
|
+
- Certificate validation
|
110
|
+
- Token validation
|
111
|
+
- mTLS validation
|
112
|
+
- SSL validation
|
113
|
+
"""
|
114
|
+
|
115
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
116
|
+
"""
|
117
|
+
Initialize authentication validator.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
config: Configuration dictionary for validation settings
|
121
|
+
"""
|
122
|
+
self.config = config or {}
|
123
|
+
self.logger = logging.getLogger(__name__)
|
124
|
+
|
125
|
+
# Custom OID for roles
|
126
|
+
self.role_oid = "1.3.6.1.4.1.99999.1"
|
127
|
+
|
128
|
+
def validate_auth(self, auth_data: Dict[str, Any], auth_type: str = "auto") -> AuthValidationResult:
|
129
|
+
"""
|
130
|
+
Universal authentication validation.
|
131
|
+
|
132
|
+
Logic:
|
133
|
+
1. Check SSL configuration
|
134
|
+
2. If SSL disabled - return success
|
135
|
+
3. Determine validation type (auto/ssl/mtls/token)
|
136
|
+
4. Check for required data
|
137
|
+
5. Perform validation
|
138
|
+
6. Return result with JSON-RPC error code
|
139
|
+
|
140
|
+
Args:
|
141
|
+
auth_data: Authentication data dictionary
|
142
|
+
auth_type: Type of authentication to validate
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
Authentication validation result
|
146
|
+
"""
|
147
|
+
try:
|
148
|
+
# Determine validation type
|
149
|
+
if auth_type == "auto":
|
150
|
+
auth_type = self._get_validation_mode()
|
151
|
+
|
152
|
+
# Check for unsupported types first
|
153
|
+
if auth_type not in ["certificate", "token", "mtls", "ssl"]:
|
154
|
+
return AuthValidationResult(
|
155
|
+
is_valid=False,
|
156
|
+
error_code=-32602,
|
157
|
+
error_message=f"Unsupported authentication type: {auth_type}"
|
158
|
+
)
|
159
|
+
|
160
|
+
# Check if authentication is enabled for the determined type
|
161
|
+
if not self._check_config(auth_type):
|
162
|
+
return AuthValidationResult(
|
163
|
+
is_valid=True,
|
164
|
+
roles=[]
|
165
|
+
)
|
166
|
+
|
167
|
+
# Validate based on type
|
168
|
+
if auth_type == "certificate":
|
169
|
+
return self.validate_certificate(
|
170
|
+
auth_data.get("cert_path"),
|
171
|
+
auth_data.get("cert_type", "server")
|
172
|
+
)
|
173
|
+
elif auth_type == "token":
|
174
|
+
return self.validate_token(
|
175
|
+
auth_data.get("token"),
|
176
|
+
auth_data.get("token_type", "jwt")
|
177
|
+
)
|
178
|
+
elif auth_type == "mtls":
|
179
|
+
return self.validate_mtls(
|
180
|
+
auth_data.get("client_cert"),
|
181
|
+
auth_data.get("ca_cert")
|
182
|
+
)
|
183
|
+
elif auth_type == "ssl":
|
184
|
+
return self.validate_ssl(
|
185
|
+
auth_data.get("server_cert")
|
186
|
+
)
|
187
|
+
|
188
|
+
except Exception as e:
|
189
|
+
self.logger.error(f"Authentication validation error: {e}")
|
190
|
+
return AuthValidationResult(
|
191
|
+
is_valid=False,
|
192
|
+
error_code=-32603,
|
193
|
+
error_message=f"Internal validation error: {str(e)}"
|
194
|
+
)
|
195
|
+
|
196
|
+
def validate_certificate(self, cert_path: Optional[str], cert_type: str = "server") -> AuthValidationResult:
|
197
|
+
"""
|
198
|
+
Validate certificate.
|
199
|
+
|
200
|
+
Args:
|
201
|
+
cert_path: Path to certificate file
|
202
|
+
cert_type: Type of certificate (server/client/ca)
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
Certificate validation result
|
206
|
+
"""
|
207
|
+
try:
|
208
|
+
if not cert_path:
|
209
|
+
return AuthValidationResult(
|
210
|
+
is_valid=False,
|
211
|
+
error_code=-32009,
|
212
|
+
error_message="Certificate path not provided"
|
213
|
+
)
|
214
|
+
|
215
|
+
if not os.path.exists(cert_path):
|
216
|
+
return AuthValidationResult(
|
217
|
+
is_valid=False,
|
218
|
+
error_code=-32009,
|
219
|
+
error_message=f"Certificate file not found: {cert_path}"
|
220
|
+
)
|
221
|
+
|
222
|
+
# Load and validate certificate
|
223
|
+
with open(cert_path, 'rb') as f:
|
224
|
+
cert_data = f.read()
|
225
|
+
|
226
|
+
cert = x509.load_pem_x509_certificate(cert_data)
|
227
|
+
|
228
|
+
# Check if certificate is not expired
|
229
|
+
now = datetime.utcnow()
|
230
|
+
if cert.not_valid_before > now or cert.not_valid_after < now:
|
231
|
+
return AuthValidationResult(
|
232
|
+
is_valid=False,
|
233
|
+
error_code=-32008,
|
234
|
+
error_message="Certificate has expired"
|
235
|
+
)
|
236
|
+
|
237
|
+
# Extract roles from certificate
|
238
|
+
roles = self._extract_roles_from_certificate(cert)
|
239
|
+
|
240
|
+
# Validate certificate type
|
241
|
+
if cert_type == "server":
|
242
|
+
# Check for server-specific extensions
|
243
|
+
if not self._validate_server_certificate(cert):
|
244
|
+
return AuthValidationResult(
|
245
|
+
is_valid=False,
|
246
|
+
error_code=-32003,
|
247
|
+
error_message="Invalid server certificate"
|
248
|
+
)
|
249
|
+
elif cert_type == "client":
|
250
|
+
# Check for client-specific extensions
|
251
|
+
if not self._validate_client_certificate(cert):
|
252
|
+
return AuthValidationResult(
|
253
|
+
is_valid=False,
|
254
|
+
error_code=-32003,
|
255
|
+
error_message="Invalid client certificate"
|
256
|
+
)
|
257
|
+
|
258
|
+
return AuthValidationResult(
|
259
|
+
is_valid=True,
|
260
|
+
roles=roles
|
261
|
+
)
|
262
|
+
|
263
|
+
except Exception as e:
|
264
|
+
self.logger.error(f"Certificate validation error: {e}")
|
265
|
+
return AuthValidationResult(
|
266
|
+
is_valid=False,
|
267
|
+
error_code=-32003,
|
268
|
+
error_message=f"Certificate validation failed: {str(e)}"
|
269
|
+
)
|
270
|
+
|
271
|
+
def validate_token(self, token: Optional[str], token_type: str = "jwt") -> AuthValidationResult:
|
272
|
+
"""
|
273
|
+
Validate token.
|
274
|
+
|
275
|
+
Args:
|
276
|
+
token: Token string to validate
|
277
|
+
token_type: Type of token (jwt/api)
|
278
|
+
|
279
|
+
Returns:
|
280
|
+
Token validation result
|
281
|
+
"""
|
282
|
+
try:
|
283
|
+
if not token:
|
284
|
+
return AuthValidationResult(
|
285
|
+
is_valid=False,
|
286
|
+
error_code=-32011,
|
287
|
+
error_message="Token not provided"
|
288
|
+
)
|
289
|
+
|
290
|
+
if token_type == "jwt":
|
291
|
+
return self._validate_jwt_token(token)
|
292
|
+
elif token_type == "api":
|
293
|
+
return self._validate_api_token(token)
|
294
|
+
else:
|
295
|
+
return AuthValidationResult(
|
296
|
+
is_valid=False,
|
297
|
+
error_code=-32602,
|
298
|
+
error_message=f"Unsupported token type: {token_type}"
|
299
|
+
)
|
300
|
+
|
301
|
+
except Exception as e:
|
302
|
+
self.logger.error(f"Token validation error: {e}")
|
303
|
+
return AuthValidationResult(
|
304
|
+
is_valid=False,
|
305
|
+
error_code=-32004,
|
306
|
+
error_message=f"Token validation failed: {str(e)}"
|
307
|
+
)
|
308
|
+
|
309
|
+
def validate_mtls(self, client_cert: Optional[str], ca_cert: Optional[str]) -> AuthValidationResult:
|
310
|
+
"""
|
311
|
+
Validate mTLS connection.
|
312
|
+
|
313
|
+
Args:
|
314
|
+
client_cert: Path to client certificate
|
315
|
+
ca_cert: Path to CA certificate
|
316
|
+
|
317
|
+
Returns:
|
318
|
+
mTLS validation result
|
319
|
+
"""
|
320
|
+
try:
|
321
|
+
if not client_cert or not ca_cert:
|
322
|
+
return AuthValidationResult(
|
323
|
+
is_valid=False,
|
324
|
+
error_code=-32005,
|
325
|
+
error_message="Client certificate and CA certificate required for mTLS"
|
326
|
+
)
|
327
|
+
|
328
|
+
# Validate client certificate
|
329
|
+
client_result = self.validate_certificate(client_cert, "client")
|
330
|
+
if not client_result.is_valid:
|
331
|
+
return client_result
|
332
|
+
|
333
|
+
# Validate CA certificate
|
334
|
+
ca_result = self.validate_certificate(ca_cert, "ca")
|
335
|
+
if not ca_result.is_valid:
|
336
|
+
return ca_result
|
337
|
+
|
338
|
+
# Verify client certificate is signed by CA
|
339
|
+
if not self._verify_certificate_chain(client_cert, ca_cert):
|
340
|
+
return AuthValidationResult(
|
341
|
+
is_valid=False,
|
342
|
+
error_code=-32005,
|
343
|
+
error_message="Client certificate not signed by provided CA"
|
344
|
+
)
|
345
|
+
|
346
|
+
return AuthValidationResult(
|
347
|
+
is_valid=True,
|
348
|
+
roles=client_result.roles
|
349
|
+
)
|
350
|
+
|
351
|
+
except Exception as e:
|
352
|
+
self.logger.error(f"mTLS validation error: {e}")
|
353
|
+
return AuthValidationResult(
|
354
|
+
is_valid=False,
|
355
|
+
error_code=-32005,
|
356
|
+
error_message=f"mTLS validation failed: {str(e)}"
|
357
|
+
)
|
358
|
+
|
359
|
+
def validate_ssl(self, server_cert: Optional[str]) -> AuthValidationResult:
|
360
|
+
"""
|
361
|
+
Validate SSL connection.
|
362
|
+
|
363
|
+
Args:
|
364
|
+
server_cert: Path to server certificate
|
365
|
+
|
366
|
+
Returns:
|
367
|
+
SSL validation result
|
368
|
+
"""
|
369
|
+
try:
|
370
|
+
if not server_cert:
|
371
|
+
return AuthValidationResult(
|
372
|
+
is_valid=False,
|
373
|
+
error_code=-32006,
|
374
|
+
error_message="Server certificate required for SSL validation"
|
375
|
+
)
|
376
|
+
|
377
|
+
# Validate server certificate
|
378
|
+
return self.validate_certificate(server_cert, "server")
|
379
|
+
|
380
|
+
except Exception as e:
|
381
|
+
self.logger.error(f"SSL validation error: {e}")
|
382
|
+
return AuthValidationResult(
|
383
|
+
is_valid=False,
|
384
|
+
error_code=-32006,
|
385
|
+
error_message=f"SSL validation failed: {str(e)}"
|
386
|
+
)
|
387
|
+
|
388
|
+
def _check_config(self, auth_type: str) -> bool:
|
389
|
+
"""
|
390
|
+
Check if authentication is enabled in configuration.
|
391
|
+
|
392
|
+
Args:
|
393
|
+
auth_type: Type of authentication
|
394
|
+
|
395
|
+
Returns:
|
396
|
+
True if authentication is enabled, False otherwise
|
397
|
+
"""
|
398
|
+
ssl_config = self.config.get("ssl", {})
|
399
|
+
|
400
|
+
if not ssl_config.get("enabled", False):
|
401
|
+
return False
|
402
|
+
|
403
|
+
# Check specific authentication type
|
404
|
+
if auth_type == "token":
|
405
|
+
return ssl_config.get("token_auth", {}).get("enabled", False)
|
406
|
+
elif auth_type == "mtls":
|
407
|
+
return ssl_config.get("mtls", {}).get("enabled", False)
|
408
|
+
elif auth_type in ["ssl", "certificate"]:
|
409
|
+
return True
|
410
|
+
|
411
|
+
# For unsupported types, return False to indicate authentication is not enabled
|
412
|
+
return False
|
413
|
+
|
414
|
+
def _get_validation_mode(self) -> str:
|
415
|
+
"""
|
416
|
+
Get validation mode from configuration.
|
417
|
+
|
418
|
+
Returns:
|
419
|
+
Validation mode string
|
420
|
+
"""
|
421
|
+
ssl_config = self.config.get("ssl", {})
|
422
|
+
|
423
|
+
if ssl_config.get("token_auth", {}).get("enabled", False):
|
424
|
+
return "token"
|
425
|
+
elif ssl_config.get("mtls", {}).get("enabled", False):
|
426
|
+
return "mtls"
|
427
|
+
elif ssl_config.get("enabled", False):
|
428
|
+
return "ssl"
|
429
|
+
|
430
|
+
return "none"
|
431
|
+
|
432
|
+
def _extract_roles_from_certificate(self, cert: x509.Certificate) -> List[str]:
|
433
|
+
"""
|
434
|
+
Extract roles from certificate.
|
435
|
+
|
436
|
+
Args:
|
437
|
+
cert: Certificate object
|
438
|
+
|
439
|
+
Returns:
|
440
|
+
List of roles extracted from certificate
|
441
|
+
"""
|
442
|
+
try:
|
443
|
+
for extension in cert.extensions:
|
444
|
+
if extension.oid.dotted_string == self.role_oid:
|
445
|
+
roles_data = extension.value.value.decode('utf-8')
|
446
|
+
return [role.strip() for role in roles_data.split(',')]
|
447
|
+
|
448
|
+
return []
|
449
|
+
|
450
|
+
except Exception as e:
|
451
|
+
self.logger.error(f"Failed to extract roles from certificate: {e}")
|
452
|
+
return []
|
453
|
+
|
454
|
+
def _validate_server_certificate(self, cert: x509.Certificate) -> bool:
|
455
|
+
"""
|
456
|
+
Validate server certificate extensions.
|
457
|
+
|
458
|
+
Args:
|
459
|
+
cert: Certificate object
|
460
|
+
|
461
|
+
Returns:
|
462
|
+
True if server certificate is valid, False otherwise
|
463
|
+
"""
|
464
|
+
try:
|
465
|
+
# Check for server authentication key usage
|
466
|
+
for extension in cert.extensions:
|
467
|
+
if extension.oid == x509.oid.ExtensionOID.KEY_USAGE:
|
468
|
+
key_usage = extension.value
|
469
|
+
return key_usage.digital_signature and key_usage.key_encipherment
|
470
|
+
|
471
|
+
return True
|
472
|
+
|
473
|
+
except Exception as e:
|
474
|
+
self.logger.error(f"Server certificate validation error: {e}")
|
475
|
+
return False
|
476
|
+
|
477
|
+
def _validate_client_certificate(self, cert: x509.Certificate) -> bool:
|
478
|
+
"""
|
479
|
+
Validate client certificate extensions.
|
480
|
+
|
481
|
+
Args:
|
482
|
+
cert: Certificate object
|
483
|
+
|
484
|
+
Returns:
|
485
|
+
True if client certificate is valid, False otherwise
|
486
|
+
"""
|
487
|
+
try:
|
488
|
+
# Check for client authentication extended key usage
|
489
|
+
for extension in cert.extensions:
|
490
|
+
if extension.oid == x509.oid.ExtensionOID.EXTENDED_KEY_USAGE:
|
491
|
+
extended_key_usage = extension.value
|
492
|
+
return x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH in extended_key_usage
|
493
|
+
|
494
|
+
return True
|
495
|
+
|
496
|
+
except Exception as e:
|
497
|
+
self.logger.error(f"Client certificate validation error: {e}")
|
498
|
+
return False
|
499
|
+
|
500
|
+
def _validate_jwt_token(self, token: str) -> AuthValidationResult:
|
501
|
+
"""
|
502
|
+
Validate JWT token.
|
503
|
+
|
504
|
+
Args:
|
505
|
+
token: JWT token string
|
506
|
+
|
507
|
+
Returns:
|
508
|
+
JWT validation result
|
509
|
+
"""
|
510
|
+
try:
|
511
|
+
# This is a placeholder for JWT validation
|
512
|
+
# In a real implementation, you would use a JWT library
|
513
|
+
# like PyJWT to validate the token
|
514
|
+
|
515
|
+
# For now, just check if token exists and has basic format
|
516
|
+
if not token or len(token.split('.')) != 3:
|
517
|
+
return AuthValidationResult(
|
518
|
+
is_valid=False,
|
519
|
+
error_code=-32004,
|
520
|
+
error_message="Invalid JWT token format"
|
521
|
+
)
|
522
|
+
|
523
|
+
# Extract roles from JWT payload (placeholder)
|
524
|
+
roles = []
|
525
|
+
|
526
|
+
return AuthValidationResult(
|
527
|
+
is_valid=True,
|
528
|
+
roles=roles
|
529
|
+
)
|
530
|
+
|
531
|
+
except Exception as e:
|
532
|
+
return AuthValidationResult(
|
533
|
+
is_valid=False,
|
534
|
+
error_code=-32004,
|
535
|
+
error_message=f"JWT validation failed: {str(e)}"
|
536
|
+
)
|
537
|
+
|
538
|
+
def _validate_api_token(self, token: str) -> AuthValidationResult:
|
539
|
+
"""
|
540
|
+
Validate API token.
|
541
|
+
|
542
|
+
Args:
|
543
|
+
token: API token string
|
544
|
+
|
545
|
+
Returns:
|
546
|
+
API token validation result
|
547
|
+
"""
|
548
|
+
try:
|
549
|
+
# This is a placeholder for API token validation
|
550
|
+
# In a real implementation, you would validate against
|
551
|
+
# a token store or database
|
552
|
+
|
553
|
+
if not token:
|
554
|
+
return AuthValidationResult(
|
555
|
+
is_valid=False,
|
556
|
+
error_code=-32011,
|
557
|
+
error_message="API token not found"
|
558
|
+
)
|
559
|
+
|
560
|
+
# Extract roles from API token (placeholder)
|
561
|
+
roles = []
|
562
|
+
|
563
|
+
return AuthValidationResult(
|
564
|
+
is_valid=True,
|
565
|
+
roles=roles
|
566
|
+
)
|
567
|
+
|
568
|
+
except Exception as e:
|
569
|
+
return AuthValidationResult(
|
570
|
+
is_valid=False,
|
571
|
+
error_code=-32004,
|
572
|
+
error_message=f"API token validation failed: {str(e)}"
|
573
|
+
)
|
574
|
+
|
575
|
+
def _verify_certificate_chain(self, client_cert_path: str, ca_cert_path: str) -> bool:
|
576
|
+
"""
|
577
|
+
Verify that client certificate is signed by CA.
|
578
|
+
|
579
|
+
Args:
|
580
|
+
client_cert_path: Path to client certificate
|
581
|
+
ca_cert_path: Path to CA certificate
|
582
|
+
|
583
|
+
Returns:
|
584
|
+
True if certificate chain is valid, False otherwise
|
585
|
+
"""
|
586
|
+
try:
|
587
|
+
# Load client certificate
|
588
|
+
with open(client_cert_path, 'rb') as f:
|
589
|
+
client_cert_data = f.read()
|
590
|
+
client_cert = x509.load_pem_x509_certificate(client_cert_data)
|
591
|
+
|
592
|
+
# Load CA certificate
|
593
|
+
with open(ca_cert_path, 'rb') as f:
|
594
|
+
ca_cert_data = f.read()
|
595
|
+
ca_cert = x509.load_pem_x509_certificate(ca_cert_data)
|
596
|
+
|
597
|
+
# Verify client certificate is signed by CA
|
598
|
+
ca_public_key = ca_cert.public_key()
|
599
|
+
|
600
|
+
# This is a simplified verification
|
601
|
+
# In a real implementation, you would use proper signature verification
|
602
|
+
return True
|
603
|
+
|
604
|
+
except Exception as e:
|
605
|
+
self.logger.error(f"Certificate chain verification error: {e}")
|
606
|
+
return False
|