mcp-proxy-adapter 6.2.34__py3-none-any.whl → 6.2.36__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/middleware/user_info_middleware.py +40 -21
- mcp_proxy_adapter/config.py +1 -0
- mcp_proxy_adapter/core/config_validator.py +17 -0
- mcp_proxy_adapter/core/crl_utils.py +335 -0
- mcp_proxy_adapter/core/security_integration.py +68 -20
- mcp_proxy_adapter/core/ssl_utils.py +37 -5
- mcp_proxy_adapter/examples/scripts/config_generator.py +1 -0
- mcp_proxy_adapter/utils/config_generator.py +1 -0
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.2.34.dist-info → mcp_proxy_adapter-6.2.36.dist-info}/METADATA +1 -1
- {mcp_proxy_adapter-6.2.34.dist-info → mcp_proxy_adapter-6.2.36.dist-info}/RECORD +15 -14
- {mcp_proxy_adapter-6.2.34.dist-info → mcp_proxy_adapter-6.2.36.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.2.34.dist-info → mcp_proxy_adapter-6.2.36.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.2.34.dist-info → mcp_proxy_adapter-6.2.36.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-6.2.34.dist-info → mcp_proxy_adapter-6.2.36.dist-info}/top_level.txt +0 -0
@@ -57,6 +57,9 @@ class UserInfoMiddleware(BaseHTTPMiddleware):
|
|
57
57
|
auth_config = security_config.get("auth", {})
|
58
58
|
permissions_config = security_config.get("permissions", {})
|
59
59
|
|
60
|
+
# Check if permissions are enabled
|
61
|
+
permissions_enabled = permissions_config.get("enabled", False)
|
62
|
+
|
60
63
|
# Create AuthConfig for mcp_security_framework
|
61
64
|
mcp_auth_config = AuthConfig(
|
62
65
|
enabled=True,
|
@@ -64,27 +67,43 @@ class UserInfoMiddleware(BaseHTTPMiddleware):
|
|
64
67
|
api_keys=auth_config.get("api_keys", {})
|
65
68
|
)
|
66
69
|
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
"
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
70
|
+
# Initialize PermissionManager only if permissions are enabled
|
71
|
+
if permissions_enabled:
|
72
|
+
# Create PermissionConfig for mcp_security_framework
|
73
|
+
mcp_permission_config = PermissionConfig(
|
74
|
+
roles_file=permissions_config.get("roles_file"),
|
75
|
+
default_role=permissions_config.get("default_role", "guest"),
|
76
|
+
admin_role=permissions_config.get("admin_role", "admin"),
|
77
|
+
role_hierarchy=permissions_config.get("role_hierarchy", {}),
|
78
|
+
permission_cache_enabled=permissions_config.get(
|
79
|
+
"permission_cache_enabled", True
|
80
|
+
),
|
81
|
+
permission_cache_ttl=permissions_config.get(
|
82
|
+
"permission_cache_ttl", 300
|
83
|
+
),
|
84
|
+
wildcard_permissions=permissions_config.get(
|
85
|
+
"wildcard_permissions", False
|
86
|
+
),
|
87
|
+
strict_mode=permissions_config.get("strict_mode", True),
|
88
|
+
roles=permissions_config.get("roles", {})
|
89
|
+
)
|
90
|
+
|
91
|
+
# Initialize PermissionManager first
|
92
|
+
self.permission_manager = PermissionManager(mcp_permission_config)
|
93
|
+
else:
|
94
|
+
# Create a minimal PermissionManager when permissions are disabled
|
95
|
+
mcp_permission_config = PermissionConfig(
|
96
|
+
roles_file=None,
|
97
|
+
default_role="guest",
|
98
|
+
admin_role="admin",
|
99
|
+
role_hierarchy={},
|
100
|
+
permission_cache_enabled=False,
|
101
|
+
permission_cache_ttl=300,
|
102
|
+
wildcard_permissions=False,
|
103
|
+
strict_mode=False,
|
104
|
+
roles={}
|
105
|
+
)
|
106
|
+
self.permission_manager = PermissionManager(mcp_permission_config)
|
88
107
|
|
89
108
|
# Initialize AuthManager with permission_manager
|
90
109
|
self.auth_manager = AuthManager(
|
mcp_proxy_adapter/config.py
CHANGED
@@ -108,6 +108,23 @@ class ConfigValidator:
|
|
108
108
|
security_enabled = security_config.get("enabled", True)
|
109
109
|
auth_enabled = self.config.get("auth_enabled", False)
|
110
110
|
|
111
|
+
# Validate permissions configuration
|
112
|
+
permissions_config = security_config.get("permissions", {})
|
113
|
+
permissions_enabled = permissions_config.get("enabled", False)
|
114
|
+
|
115
|
+
if permissions_enabled:
|
116
|
+
# Permissions require authentication to identify users
|
117
|
+
auth_config = security_config.get("auth", {})
|
118
|
+
if not auth_config.get("enabled", False):
|
119
|
+
self.errors.append("Permissions are enabled but authentication is disabled. Permissions require authentication to identify users.")
|
120
|
+
return False
|
121
|
+
|
122
|
+
# Check if there are any authentication methods available
|
123
|
+
auth_methods = auth_config.get("methods", [])
|
124
|
+
if not auth_methods:
|
125
|
+
self.errors.append("Permissions are enabled but no authentication methods are configured. At least one authentication method is required.")
|
126
|
+
return False
|
127
|
+
|
111
128
|
if security_enabled and auth_enabled:
|
112
129
|
# Validate auth configuration
|
113
130
|
auth_config = security_config.get("auth", {})
|
@@ -0,0 +1,335 @@
|
|
1
|
+
"""
|
2
|
+
CRL Utilities Module
|
3
|
+
|
4
|
+
This module provides utilities for working with Certificate Revocation Lists (CRL).
|
5
|
+
Supports both file-based and URL-based CRL sources.
|
6
|
+
|
7
|
+
Author: Vasiliy Zdanovskiy
|
8
|
+
email: vasilyvz@gmail.com
|
9
|
+
Version: 1.0.0
|
10
|
+
"""
|
11
|
+
|
12
|
+
import logging
|
13
|
+
import os
|
14
|
+
import tempfile
|
15
|
+
from pathlib import Path
|
16
|
+
from typing import Optional, Union, Dict, Any
|
17
|
+
from urllib.parse import urlparse
|
18
|
+
import requests
|
19
|
+
from requests.adapters import HTTPAdapter
|
20
|
+
from urllib3.util.retry import Retry
|
21
|
+
|
22
|
+
# Import mcp_security_framework CRL utilities
|
23
|
+
try:
|
24
|
+
from mcp_security_framework.utils.cert_utils import (
|
25
|
+
parse_crl,
|
26
|
+
is_certificate_revoked,
|
27
|
+
validate_certificate_against_crl,
|
28
|
+
is_crl_valid,
|
29
|
+
get_crl_info
|
30
|
+
)
|
31
|
+
SECURITY_FRAMEWORK_AVAILABLE = True
|
32
|
+
except ImportError:
|
33
|
+
SECURITY_FRAMEWORK_AVAILABLE = False
|
34
|
+
|
35
|
+
logger = logging.getLogger(__name__)
|
36
|
+
|
37
|
+
|
38
|
+
class CRLManager:
|
39
|
+
"""
|
40
|
+
Manager for Certificate Revocation Lists (CRL).
|
41
|
+
|
42
|
+
Supports both file-based and URL-based CRL sources.
|
43
|
+
Automatically downloads CRL from URLs and caches them locally.
|
44
|
+
"""
|
45
|
+
|
46
|
+
def __init__(self, config: Dict[str, Any]):
|
47
|
+
"""
|
48
|
+
Initialize CRL manager.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
config: Configuration dictionary containing CRL settings
|
52
|
+
"""
|
53
|
+
self.config = config
|
54
|
+
self.crl_enabled = config.get("crl_enabled", False)
|
55
|
+
self.crl_path = config.get("crl_path")
|
56
|
+
self.crl_url = config.get("crl_url")
|
57
|
+
self.crl_validity_days = config.get("crl_validity_days", 30)
|
58
|
+
|
59
|
+
# Cache for downloaded CRL files
|
60
|
+
self._crl_cache: Dict[str, str] = {}
|
61
|
+
|
62
|
+
# Setup HTTP session with retry strategy
|
63
|
+
self._setup_http_session()
|
64
|
+
|
65
|
+
logger.info(f"CRL Manager initialized - enabled: {self.crl_enabled}")
|
66
|
+
|
67
|
+
def _setup_http_session(self):
|
68
|
+
"""Setup HTTP session with retry strategy for CRL downloads."""
|
69
|
+
self.session = requests.Session()
|
70
|
+
|
71
|
+
# Configure retry strategy
|
72
|
+
retry_strategy = Retry(
|
73
|
+
total=3,
|
74
|
+
backoff_factor=1,
|
75
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
76
|
+
)
|
77
|
+
|
78
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
79
|
+
self.session.mount("http://", adapter)
|
80
|
+
self.session.mount("https://", adapter)
|
81
|
+
|
82
|
+
# Set timeout
|
83
|
+
self.session.timeout = 30
|
84
|
+
|
85
|
+
def get_crl_data(self) -> Optional[Union[str, bytes, Path]]:
|
86
|
+
"""
|
87
|
+
Get CRL data from configured source.
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
CRL data as string, bytes, or Path, or None if not available
|
91
|
+
|
92
|
+
Raises:
|
93
|
+
ValueError: If CRL is enabled but no source is configured
|
94
|
+
FileNotFoundError: If CRL file is not found
|
95
|
+
requests.RequestException: If CRL download fails
|
96
|
+
"""
|
97
|
+
if not self.crl_enabled:
|
98
|
+
logger.debug("CRL is disabled, skipping CRL check")
|
99
|
+
return None
|
100
|
+
|
101
|
+
# Check if CRL URL is configured
|
102
|
+
if self.crl_url:
|
103
|
+
return self._get_crl_from_url()
|
104
|
+
|
105
|
+
# Check if CRL file path is configured
|
106
|
+
if self.crl_path:
|
107
|
+
return self._get_crl_from_file()
|
108
|
+
|
109
|
+
# If CRL is enabled but no source is configured, this is an error
|
110
|
+
if self.crl_enabled:
|
111
|
+
raise ValueError("CRL is enabled but neither crl_path nor crl_url is configured")
|
112
|
+
|
113
|
+
return None
|
114
|
+
|
115
|
+
def _get_crl_from_url(self) -> str:
|
116
|
+
"""
|
117
|
+
Download CRL from URL.
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
Path to downloaded CRL file
|
121
|
+
|
122
|
+
Raises:
|
123
|
+
requests.RequestException: If download fails
|
124
|
+
ValueError: If downloaded data is not valid CRL
|
125
|
+
"""
|
126
|
+
try:
|
127
|
+
logger.info(f"Downloading CRL from URL: {self.crl_url}")
|
128
|
+
|
129
|
+
# Download CRL
|
130
|
+
response = self.session.get(self.crl_url)
|
131
|
+
response.raise_for_status()
|
132
|
+
|
133
|
+
# Validate content type
|
134
|
+
content_type = response.headers.get('content-type', '').lower()
|
135
|
+
if 'application/pkix-crl' not in content_type and 'application/x-pkcs7-crl' not in content_type:
|
136
|
+
logger.warning(f"Unexpected content type for CRL: {content_type}")
|
137
|
+
|
138
|
+
# Save to temporary file
|
139
|
+
with tempfile.NamedTemporaryFile(mode='wb', suffix='.crl', delete=False) as temp_file:
|
140
|
+
temp_file.write(response.content)
|
141
|
+
temp_file_path = temp_file.name
|
142
|
+
|
143
|
+
# Validate CRL format
|
144
|
+
if SECURITY_FRAMEWORK_AVAILABLE:
|
145
|
+
try:
|
146
|
+
is_crl_valid(temp_file_path)
|
147
|
+
logger.info(f"CRL downloaded and validated successfully from {self.crl_url}")
|
148
|
+
except Exception as e:
|
149
|
+
os.unlink(temp_file_path)
|
150
|
+
raise ValueError(f"Downloaded CRL is not valid: {e}")
|
151
|
+
else:
|
152
|
+
logger.warning("mcp_security_framework not available, skipping CRL validation")
|
153
|
+
|
154
|
+
# Cache the file path
|
155
|
+
self._crl_cache[self.crl_url] = temp_file_path
|
156
|
+
|
157
|
+
return temp_file_path
|
158
|
+
|
159
|
+
except requests.RequestException as e:
|
160
|
+
logger.error(f"Failed to download CRL from {self.crl_url}: {e}")
|
161
|
+
raise
|
162
|
+
except Exception as e:
|
163
|
+
logger.error(f"CRL download failed: {e}")
|
164
|
+
raise
|
165
|
+
|
166
|
+
def _get_crl_from_file(self) -> str:
|
167
|
+
"""
|
168
|
+
Get CRL from file path.
|
169
|
+
|
170
|
+
Returns:
|
171
|
+
Path to CRL file
|
172
|
+
|
173
|
+
Raises:
|
174
|
+
FileNotFoundError: If CRL file is not found
|
175
|
+
ValueError: If CRL file is not valid
|
176
|
+
"""
|
177
|
+
if not os.path.exists(self.crl_path):
|
178
|
+
raise FileNotFoundError(f"CRL file not found: {self.crl_path}")
|
179
|
+
|
180
|
+
# Validate CRL format
|
181
|
+
if SECURITY_FRAMEWORK_AVAILABLE:
|
182
|
+
try:
|
183
|
+
is_crl_valid(self.crl_path)
|
184
|
+
logger.info(f"CRL file validated successfully: {self.crl_path}")
|
185
|
+
except Exception as e:
|
186
|
+
raise ValueError(f"CRL file is not valid: {e}")
|
187
|
+
else:
|
188
|
+
logger.warning("mcp_security_framework not available, skipping CRL validation")
|
189
|
+
|
190
|
+
return self.crl_path
|
191
|
+
|
192
|
+
def is_certificate_revoked(self, cert_path: str) -> bool:
|
193
|
+
"""
|
194
|
+
Check if certificate is revoked according to CRL.
|
195
|
+
|
196
|
+
Args:
|
197
|
+
cert_path: Path to certificate file
|
198
|
+
|
199
|
+
Returns:
|
200
|
+
True if certificate is revoked, False otherwise
|
201
|
+
|
202
|
+
Raises:
|
203
|
+
ValueError: If CRL is enabled but not available
|
204
|
+
FileNotFoundError: If certificate file is not found
|
205
|
+
"""
|
206
|
+
if not self.crl_enabled:
|
207
|
+
return False
|
208
|
+
|
209
|
+
if not SECURITY_FRAMEWORK_AVAILABLE:
|
210
|
+
logger.warning("mcp_security_framework not available, skipping CRL check")
|
211
|
+
return False
|
212
|
+
|
213
|
+
try:
|
214
|
+
crl_data = self.get_crl_data()
|
215
|
+
if not crl_data:
|
216
|
+
logger.warning("CRL is enabled but no CRL data is available")
|
217
|
+
return False
|
218
|
+
|
219
|
+
is_revoked = is_certificate_revoked(cert_path, crl_data)
|
220
|
+
|
221
|
+
if is_revoked:
|
222
|
+
logger.warning(f"Certificate is revoked according to CRL: {cert_path}")
|
223
|
+
else:
|
224
|
+
logger.debug(f"Certificate is not revoked according to CRL: {cert_path}")
|
225
|
+
|
226
|
+
return is_revoked
|
227
|
+
|
228
|
+
except Exception as e:
|
229
|
+
logger.error(f"CRL check failed for certificate {cert_path}: {e}")
|
230
|
+
# For security, consider certificate invalid if CRL check fails
|
231
|
+
return True
|
232
|
+
|
233
|
+
def validate_certificate_against_crl(self, cert_path: str) -> Dict[str, Any]:
|
234
|
+
"""
|
235
|
+
Validate certificate against CRL and return detailed status.
|
236
|
+
|
237
|
+
Args:
|
238
|
+
cert_path: Path to certificate file
|
239
|
+
|
240
|
+
Returns:
|
241
|
+
Dictionary containing validation results
|
242
|
+
|
243
|
+
Raises:
|
244
|
+
ValueError: If CRL is enabled but not available
|
245
|
+
FileNotFoundError: If certificate file is not found
|
246
|
+
"""
|
247
|
+
if not self.crl_enabled:
|
248
|
+
return {
|
249
|
+
"is_revoked": False,
|
250
|
+
"crl_checked": False,
|
251
|
+
"crl_source": None,
|
252
|
+
"message": "CRL check is disabled"
|
253
|
+
}
|
254
|
+
|
255
|
+
if not SECURITY_FRAMEWORK_AVAILABLE:
|
256
|
+
logger.warning("mcp_security_framework not available, skipping CRL validation")
|
257
|
+
return {
|
258
|
+
"is_revoked": False,
|
259
|
+
"crl_checked": False,
|
260
|
+
"crl_source": None,
|
261
|
+
"message": "mcp_security_framework not available"
|
262
|
+
}
|
263
|
+
|
264
|
+
try:
|
265
|
+
crl_data = self.get_crl_data()
|
266
|
+
if not crl_data:
|
267
|
+
logger.warning("CRL is enabled but no CRL data is available")
|
268
|
+
return {
|
269
|
+
"is_revoked": True, # For security, consider invalid if CRL unavailable
|
270
|
+
"crl_checked": False,
|
271
|
+
"crl_source": None,
|
272
|
+
"message": "CRL is enabled but not available"
|
273
|
+
}
|
274
|
+
|
275
|
+
# Get CRL source info
|
276
|
+
crl_source = self.crl_url if self.crl_url else self.crl_path
|
277
|
+
|
278
|
+
# Validate certificate against CRL
|
279
|
+
result = validate_certificate_against_crl(cert_path, crl_data)
|
280
|
+
|
281
|
+
result["crl_checked"] = True
|
282
|
+
result["crl_source"] = crl_source
|
283
|
+
|
284
|
+
return result
|
285
|
+
|
286
|
+
except Exception as e:
|
287
|
+
logger.error(f"CRL validation failed for certificate {cert_path}: {e}")
|
288
|
+
# For security, consider certificate invalid if CRL validation fails
|
289
|
+
return {
|
290
|
+
"is_revoked": True,
|
291
|
+
"crl_checked": False,
|
292
|
+
"crl_source": self.crl_url if self.crl_url else self.crl_path,
|
293
|
+
"message": f"CRL validation failed: {e}"
|
294
|
+
}
|
295
|
+
|
296
|
+
def get_crl_info(self) -> Optional[Dict[str, Any]]:
|
297
|
+
"""
|
298
|
+
Get information about the configured CRL.
|
299
|
+
|
300
|
+
Returns:
|
301
|
+
Dictionary containing CRL information, or None if CRL is not available
|
302
|
+
"""
|
303
|
+
if not self.crl_enabled:
|
304
|
+
return None
|
305
|
+
|
306
|
+
if not SECURITY_FRAMEWORK_AVAILABLE:
|
307
|
+
logger.warning("mcp_security_framework not available, cannot get CRL info")
|
308
|
+
return None
|
309
|
+
|
310
|
+
try:
|
311
|
+
crl_data = self.get_crl_data()
|
312
|
+
if not crl_data:
|
313
|
+
return None
|
314
|
+
|
315
|
+
return get_crl_info(crl_data)
|
316
|
+
|
317
|
+
except Exception as e:
|
318
|
+
logger.error(f"Failed to get CRL info: {e}")
|
319
|
+
return None
|
320
|
+
|
321
|
+
def cleanup_cache(self):
|
322
|
+
"""Clean up temporary CRL files."""
|
323
|
+
for url, temp_path in self._crl_cache.items():
|
324
|
+
try:
|
325
|
+
if os.path.exists(temp_path):
|
326
|
+
os.unlink(temp_path)
|
327
|
+
logger.debug(f"Cleaned up temporary CRL file: {temp_path}")
|
328
|
+
except Exception as e:
|
329
|
+
logger.warning(f"Failed to cleanup temporary CRL file {temp_path}: {e}")
|
330
|
+
|
331
|
+
self._crl_cache.clear()
|
332
|
+
|
333
|
+
def __del__(self):
|
334
|
+
"""Cleanup when object is destroyed."""
|
335
|
+
self.cleanup_cache()
|
@@ -105,25 +105,42 @@ class SecurityIntegration:
|
|
105
105
|
|
106
106
|
# Create permission config - handle null values properly
|
107
107
|
permissions_section = security_section.get("permissions", {})
|
108
|
-
|
109
|
-
|
110
|
-
# If roles_file is None or empty string, don't pass it to avoid framework errors
|
111
|
-
if roles_file is None or roles_file == "":
|
112
|
-
logger.warning("roles_file is None or empty, permissions will use default configuration")
|
113
|
-
roles_file = None
|
108
|
+
permissions_enabled = permissions_section.get("enabled", True)
|
114
109
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
110
|
+
if permissions_enabled:
|
111
|
+
roles_file = permissions_section.get("roles_file")
|
112
|
+
|
113
|
+
# If roles_file is None or empty string, don't pass it to avoid framework errors
|
114
|
+
if roles_file is None or roles_file == "":
|
115
|
+
logger.warning("roles_file is None or empty, permissions will use default configuration")
|
116
|
+
roles_file = None
|
117
|
+
|
118
|
+
permission_config = PermissionConfig(
|
119
|
+
enabled=True,
|
120
|
+
roles_file=roles_file,
|
121
|
+
default_role=permissions_section.get("default_role", "guest"),
|
122
|
+
admin_role=permissions_section.get("admin_role", "admin"),
|
123
|
+
role_hierarchy=permissions_section.get("role_hierarchy", {}),
|
124
|
+
permission_cache_enabled=permissions_section.get("permission_cache_enabled", True),
|
125
|
+
permission_cache_ttl=permissions_section.get("permission_cache_ttl", 300),
|
126
|
+
wildcard_permissions=permissions_section.get("wildcard_permissions", False),
|
127
|
+
strict_mode=permissions_section.get("strict_mode", True),
|
128
|
+
roles=permissions_section.get("roles")
|
129
|
+
)
|
130
|
+
else:
|
131
|
+
# Create minimal permission config when permissions are disabled
|
132
|
+
permission_config = PermissionConfig(
|
133
|
+
enabled=False,
|
134
|
+
roles_file=None,
|
135
|
+
default_role="guest",
|
136
|
+
admin_role="admin",
|
137
|
+
role_hierarchy={},
|
138
|
+
permission_cache_enabled=False,
|
139
|
+
permission_cache_ttl=300,
|
140
|
+
wildcard_permissions=False,
|
141
|
+
strict_mode=False,
|
142
|
+
roles={}
|
143
|
+
)
|
127
144
|
|
128
145
|
# Create rate limit config
|
129
146
|
rate_limit_config = RateLimitConfig(
|
@@ -202,8 +219,39 @@ class SecurityIntegration:
|
|
202
219
|
return await self.certificate_manager.create_server_certificate(common_name, **kwargs)
|
203
220
|
|
204
221
|
async def validate_certificate(self, cert_path: str) -> bool:
|
205
|
-
"""Validate certificate."""
|
206
|
-
|
222
|
+
"""Validate certificate with CRL check if enabled."""
|
223
|
+
try:
|
224
|
+
# Get CRL configuration from security config
|
225
|
+
crl_config = None
|
226
|
+
if hasattr(self.security_config, 'certificates'):
|
227
|
+
cert_config = self.security_config.certificates
|
228
|
+
if hasattr(cert_config, 'crl_enabled') and cert_config.crl_enabled:
|
229
|
+
crl_config = {
|
230
|
+
'crl_enabled': cert_config.crl_enabled,
|
231
|
+
'crl_path': getattr(cert_config, 'crl_path', None),
|
232
|
+
'crl_url': getattr(cert_config, 'crl_url', None),
|
233
|
+
'crl_validity_days': getattr(cert_config, 'crl_validity_days', 30)
|
234
|
+
}
|
235
|
+
|
236
|
+
# Use mcp_security_framework's validate_certificate_chain with CRL
|
237
|
+
if crl_config and crl_config.get('crl_enabled'):
|
238
|
+
from mcp_security_framework.utils.cert_utils import validate_certificate_chain
|
239
|
+
from .crl_utils import CRLManager
|
240
|
+
|
241
|
+
# Get CRL data
|
242
|
+
crl_manager = CRLManager(crl_config)
|
243
|
+
crl_data = crl_manager.get_crl_data()
|
244
|
+
|
245
|
+
# Validate with CRL
|
246
|
+
if crl_data:
|
247
|
+
return validate_certificate_chain(cert_path, self.security_config.certificates.ca_cert_path, crl_data)
|
248
|
+
|
249
|
+
# Fallback to standard validation
|
250
|
+
return await self.certificate_manager.validate_certificate(cert_path)
|
251
|
+
|
252
|
+
except Exception as e:
|
253
|
+
logger.error(f"Certificate validation failed: {e}")
|
254
|
+
return False
|
207
255
|
|
208
256
|
async def extract_roles_from_certificate(self, cert_path: str) -> List[str]:
|
209
257
|
"""Extract roles from certificate."""
|
@@ -3,8 +3,10 @@ SSL Utilities Module
|
|
3
3
|
|
4
4
|
This module provides utilities for SSL/TLS configuration and certificate validation.
|
5
5
|
Integrates with AuthValidator from Phase 0 for certificate validation.
|
6
|
+
Supports CRL (Certificate Revocation List) validation.
|
6
7
|
|
7
|
-
Author:
|
8
|
+
Author: Vasiliy Zdanovskiy
|
9
|
+
email: vasilyvz@gmail.com
|
8
10
|
Version: 1.0.0
|
9
11
|
"""
|
10
12
|
|
@@ -14,6 +16,7 @@ from typing import List, Optional, Dict, Any
|
|
14
16
|
from pathlib import Path
|
15
17
|
|
16
18
|
from .auth_validator import AuthValidator
|
19
|
+
from .crl_utils import CRLManager
|
17
20
|
|
18
21
|
logger = logging.getLogger(__name__)
|
19
22
|
|
@@ -47,7 +50,8 @@ class SSLUtils:
|
|
47
50
|
verify_client: bool = False,
|
48
51
|
cipher_suites: Optional[List[str]] = None,
|
49
52
|
min_tls_version: str = "1.2",
|
50
|
-
max_tls_version: str = "1.3"
|
53
|
+
max_tls_version: str = "1.3",
|
54
|
+
crl_config: Optional[Dict[str, Any]] = None) -> ssl.SSLContext:
|
51
55
|
"""
|
52
56
|
Create SSL context with specified configuration.
|
53
57
|
|
@@ -59,6 +63,7 @@ class SSLUtils:
|
|
59
63
|
cipher_suites: List of cipher suites to use
|
60
64
|
min_tls_version: Minimum TLS version
|
61
65
|
max_tls_version: Maximum TLS version
|
66
|
+
crl_config: CRL configuration dictionary (optional)
|
62
67
|
|
63
68
|
Returns:
|
64
69
|
Configured SSL context
|
@@ -73,6 +78,17 @@ class SSLUtils:
|
|
73
78
|
if not result.is_valid:
|
74
79
|
raise ValueError(f"Invalid certificate: {result.error_message}")
|
75
80
|
|
81
|
+
# Check CRL if configured
|
82
|
+
if crl_config:
|
83
|
+
try:
|
84
|
+
crl_manager = CRLManager(crl_config)
|
85
|
+
if crl_manager.is_certificate_revoked(cert_file):
|
86
|
+
raise ValueError(f"Certificate is revoked according to CRL: {cert_file}")
|
87
|
+
except Exception as e:
|
88
|
+
logger.error(f"CRL check failed: {e}")
|
89
|
+
# For security, fail if CRL check fails
|
90
|
+
raise ValueError(f"CRL validation failed: {e}")
|
91
|
+
|
76
92
|
# Check if files exist
|
77
93
|
if not Path(cert_file).exists():
|
78
94
|
raise FileNotFoundError(f"Certificate file not found: {cert_file}")
|
@@ -111,12 +127,13 @@ class SSLUtils:
|
|
111
127
|
return context
|
112
128
|
|
113
129
|
@staticmethod
|
114
|
-
def validate_certificate(cert_file: str) -> bool:
|
130
|
+
def validate_certificate(cert_file: str, crl_config: Optional[Dict[str, Any]] = None) -> bool:
|
115
131
|
"""
|
116
|
-
Validate certificate using AuthValidator.
|
132
|
+
Validate certificate using AuthValidator and optional CRL check.
|
117
133
|
|
118
134
|
Args:
|
119
135
|
cert_file: Path to certificate file
|
136
|
+
crl_config: CRL configuration dictionary (optional)
|
120
137
|
|
121
138
|
Returns:
|
122
139
|
True if certificate is valid, False otherwise
|
@@ -124,7 +141,22 @@ class SSLUtils:
|
|
124
141
|
try:
|
125
142
|
validator = AuthValidator()
|
126
143
|
result = validator.validate_certificate(cert_file)
|
127
|
-
|
144
|
+
if not result.is_valid:
|
145
|
+
return False
|
146
|
+
|
147
|
+
# Check CRL if configured
|
148
|
+
if crl_config:
|
149
|
+
try:
|
150
|
+
crl_manager = CRLManager(crl_config)
|
151
|
+
if crl_manager.is_certificate_revoked(cert_file):
|
152
|
+
logger.warning(f"Certificate is revoked according to CRL: {cert_file}")
|
153
|
+
return False
|
154
|
+
except Exception as e:
|
155
|
+
logger.error(f"CRL check failed: {e}")
|
156
|
+
# For security, consider certificate invalid if CRL check fails
|
157
|
+
return False
|
158
|
+
|
159
|
+
return True
|
128
160
|
except Exception as e:
|
129
161
|
logger.error(f"Certificate validation failed: {e}")
|
130
162
|
return False
|
mcp_proxy_adapter/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-proxy-adapter
|
3
|
-
Version: 6.2.
|
3
|
+
Version: 6.2.36
|
4
4
|
Summary: Powerful JSON-RPC microservices framework with built-in security, authentication, and proxy registration
|
5
5
|
Home-page: https://github.com/maverikod/mcp-proxy-adapter
|
6
6
|
Author: Vasiliy Zdanovskiy
|
@@ -1,10 +1,10 @@
|
|
1
1
|
mcp_proxy_adapter/__init__.py,sha256=B7m1YWyv_Wb87-Q-JqVpHQgwajnfIgDyZ_iIxzdTbBY,1021
|
2
2
|
mcp_proxy_adapter/__main__.py,sha256=-Wp1myP9DzJNB9j97mj62C8kFk5YUbCmd0e7Rnwte0A,769
|
3
|
-
mcp_proxy_adapter/config.py,sha256=
|
3
|
+
mcp_proxy_adapter/config.py,sha256=_VpJrmdK6NS27ABgoEABvMcgUHxCjd1D1H-HUzx3-hY,21637
|
4
4
|
mcp_proxy_adapter/custom_openapi.py,sha256=jYUrCy8C1mShh3sjKj-JkzSMLAvxDLTvtzSJFj5HUNg,15023
|
5
5
|
mcp_proxy_adapter/main.py,sha256=9qt_pEQdq8roUc73CumfDn6jDWP_NyfdE1lCGEynv5I,2841
|
6
6
|
mcp_proxy_adapter/openapi.py,sha256=36vOEbJjGnVZR6hUhl6mHCD29HYOEFKo2bL0JdGSm-4,13952
|
7
|
-
mcp_proxy_adapter/version.py,sha256=
|
7
|
+
mcp_proxy_adapter/version.py,sha256=qSSceyK6mUT_pi9OfsbciFmPwDhxJiY6FJUPRWrc-Yo,76
|
8
8
|
mcp_proxy_adapter/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
mcp_proxy_adapter/api/app.py,sha256=khl4kaI4mJ6dNbfAK7hR97Ek-eWC9NBeuXHr6GVbLoU,28911
|
10
10
|
mcp_proxy_adapter/api/handlers.py,sha256=DcZT7MVBV33q-0EJ0iFqxE0VgBkFt6d_SqoRkntwyvc,8477
|
@@ -21,7 +21,7 @@ mcp_proxy_adapter/api/middleware/performance.py,sha256=dHBxTF43LEGXMKHMH3A8ybKmw
|
|
21
21
|
mcp_proxy_adapter/api/middleware/protocol_middleware.py,sha256=iVjJrTEfKy15ZchQUo-Mu0hBg9kEP6vgzee_3PtWd6M,8115
|
22
22
|
mcp_proxy_adapter/api/middleware/transport_middleware.py,sha256=Esy2gGKpEV5RoUTilr1YKKTDc5jh5RxsomD0VXyR2pE,4396
|
23
23
|
mcp_proxy_adapter/api/middleware/unified_security.py,sha256=fDWUeIuHjYUngVnB8gVR9ES3IQSaY9VP2YPZGXATJlU,7617
|
24
|
-
mcp_proxy_adapter/api/middleware/user_info_middleware.py,sha256=
|
24
|
+
mcp_proxy_adapter/api/middleware/user_info_middleware.py,sha256=YGtt_1Q4GkvdFopMC2wTfC3OZZXDiOU4v5BxlD1Ge9w,9168
|
25
25
|
mcp_proxy_adapter/commands/__init__.py,sha256=r791wg4FKhWSi5rqA3vekDcGf5kr18pwF1woX-dnZKo,1525
|
26
26
|
mcp_proxy_adapter/commands/auth_validation_command.py,sha256=z612WJDVgZwaCrxdQhATwRc5i3qxH37MPuIV6SuZPn8,15083
|
27
27
|
mcp_proxy_adapter/commands/base.py,sha256=tunyrmt-LYJMQZslAZQor3KZvOrn1IYNpL5uOAnSdxc,15791
|
@@ -61,7 +61,8 @@ mcp_proxy_adapter/core/client.py,sha256=AeNjMyaTnnR0g6Sw_c28X_le8o5SbEi-_PaRtVtt
|
|
61
61
|
mcp_proxy_adapter/core/client_manager.py,sha256=sKEhapMpogqb54WIWEpz2bMjrX3wvYooX-a844IfCTU,9164
|
62
62
|
mcp_proxy_adapter/core/client_security.py,sha256=8isHpvv-7H85QzI8K3Pfyr_KdvpE2xYyIT4wqWrttNU,13575
|
63
63
|
mcp_proxy_adapter/core/config_converter.py,sha256=FAA2zx-yRgqMgzg73o9Aq5CEEfodNCeaA8Yluto4wAs,16985
|
64
|
-
mcp_proxy_adapter/core/config_validator.py,sha256=
|
64
|
+
mcp_proxy_adapter/core/config_validator.py,sha256=fRN0D727FYlka-Yn0x3-tABJFrXjXorWR1fAD97e02c,8722
|
65
|
+
mcp_proxy_adapter/core/crl_utils.py,sha256=_BZ5LE1Z4GBbLhQgtKWoTAtJdXNcl59y9XJvfptHBjo,11561
|
65
66
|
mcp_proxy_adapter/core/errors.py,sha256=s34OxiIR4NCJu_pYSigKXqrIvRjUUK2OWw0X4dpDjIA,5151
|
66
67
|
mcp_proxy_adapter/core/logging.py,sha256=jQlFz52Xwapef6UD4p0acmaGFumD9XuexwW4frDN_ZM,9626
|
67
68
|
mcp_proxy_adapter/core/mtls_asgi.py,sha256=X2lAj3wk3L85amRCp_-10sqvZa5wJf_diXhwrrQReSo,5311
|
@@ -72,11 +73,11 @@ mcp_proxy_adapter/core/proxy_registration.py,sha256=Mmh-hWVJKkADLP8361Nx_SGFp9JR
|
|
72
73
|
mcp_proxy_adapter/core/role_utils.py,sha256=wMoTVz3gF5fM7jozNMwsEwPkp1tui26M-t_KH1Oz8gs,12880
|
73
74
|
mcp_proxy_adapter/core/security_adapter.py,sha256=wZ3OH1WzhUdpN8N8CrGJSFFVNi474DqdazIqQ1T8PN4,13343
|
74
75
|
mcp_proxy_adapter/core/security_factory.py,sha256=4r7qvBq30XfosGD_b1ZHyNVLN8rOQ3NAKuaCOCEK8jA,8262
|
75
|
-
mcp_proxy_adapter/core/security_integration.py,sha256=
|
76
|
+
mcp_proxy_adapter/core/security_integration.py,sha256=gu5fkta4rcoWkyLoMnJ7rw6CTQLZ8JASFgFgA3C8-4A,15937
|
76
77
|
mcp_proxy_adapter/core/server_adapter.py,sha256=8dhUlLxuYjaoNgMHieFCFgDRjxskP--Y5uoAhbN6RLw,9823
|
77
78
|
mcp_proxy_adapter/core/server_engine.py,sha256=SFENSDrVMlBD--HgKSRVklhrtLKSRSZhs_3UHxFCGbg,9540
|
78
79
|
mcp_proxy_adapter/core/settings.py,sha256=ZfUnmqD1tjAuaQo2VAF8evC1oHUit7gTu4WkTF0IMYI,10628
|
79
|
-
mcp_proxy_adapter/core/ssl_utils.py,sha256=
|
80
|
+
mcp_proxy_adapter/core/ssl_utils.py,sha256=qLzeMe4pQsGX2IN_PUotcaDOtExXns32gsm9AhKKdhk,9745
|
80
81
|
mcp_proxy_adapter/core/transport_manager.py,sha256=ppcgjO-7Ulrk1ovlzlXVM89Iw4VOGA3awKgLf7FFAJ0,9518
|
81
82
|
mcp_proxy_adapter/core/unified_config_adapter.py,sha256=cpN_VrliIFGDH3JsfRkTlFdQvLcmuMYYedq0QEzlb0Y,22857
|
82
83
|
mcp_proxy_adapter/core/utils.py,sha256=ly8Ttg2v1OBukThJLxudRvmttU1hxJFLJUfat4b2dOI,3268
|
@@ -128,15 +129,15 @@ mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.
|
|
128
129
|
mcp_proxy_adapter/examples/full_application/hooks/__init__.py,sha256=ORG4cL8cSXEMmZ0CEPz75OVuwg54pdDm2GIBpP4dtcs,200
|
129
130
|
mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py,sha256=TYXuHI-KW_mH5r8mSKgNMJCr3moeEKrqC4Eex0U298k,3457
|
130
131
|
mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py,sha256=IaskSrckZS6bE3aGxSBL8aTj-iJTSI2ysfsFjhjncyM,2975
|
131
|
-
mcp_proxy_adapter/examples/scripts/config_generator.py,sha256=
|
132
|
+
mcp_proxy_adapter/examples/scripts/config_generator.py,sha256=30z8crMPDUcllwunkpigWrF6scWPIQaqV0gbuv6Juj8,32838
|
132
133
|
mcp_proxy_adapter/examples/scripts/create_certificates_simple.py,sha256=xkIvUYl6hbKlWImQmenG0k_CvIsOsc9ZHICiKY3rtI8,26380
|
133
134
|
mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py,sha256=J0qHm_BMY8RYqfuwf7V7xKsHcsRJx8E7x-8JxmW5sPw,15988
|
134
135
|
mcp_proxy_adapter/schemas/base_schema.json,sha256=v9G9cGMd4dRhCZsOQ_FMqOi5VFyVbI6Cf3fyIvOT9dc,2881
|
135
136
|
mcp_proxy_adapter/schemas/openapi_schema.json,sha256=C3yLkwmDsvnLW9B5gnKKdBGl4zxkeU-rEmjTrNVsQU0,8405
|
136
|
-
mcp_proxy_adapter/utils/config_generator.py,sha256=
|
137
|
-
mcp_proxy_adapter-6.2.
|
138
|
-
mcp_proxy_adapter-6.2.
|
139
|
-
mcp_proxy_adapter-6.2.
|
140
|
-
mcp_proxy_adapter-6.2.
|
141
|
-
mcp_proxy_adapter-6.2.
|
142
|
-
mcp_proxy_adapter-6.2.
|
137
|
+
mcp_proxy_adapter/utils/config_generator.py,sha256=0CnUuz4WMBtHY-FNB7MNS1eV6lsu_S_lpMmhefNvlEw,46502
|
138
|
+
mcp_proxy_adapter-6.2.36.dist-info/licenses/LICENSE,sha256=6KdtUcTwmTRbJrAmYjVn7e6S-V42ubeDJ-AiVEzZ510,1075
|
139
|
+
mcp_proxy_adapter-6.2.36.dist-info/METADATA,sha256=ovX3nGOSsX6_agmbvs5yBcTiJaO3w-gAdbxBee5C8ic,22348
|
140
|
+
mcp_proxy_adapter-6.2.36.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
141
|
+
mcp_proxy_adapter-6.2.36.dist-info/entry_points.txt,sha256=J3eV6ID0lt_VSp4lIdIgBFTqLCThgObNNxRCbyfiMHw,70
|
142
|
+
mcp_proxy_adapter-6.2.36.dist-info/top_level.txt,sha256=JZT7vPLBYrtroX-ij68JBhJYbjDdghcV-DFySRy-Nnw,18
|
143
|
+
mcp_proxy_adapter-6.2.36.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|