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.
@@ -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
- # Create PermissionConfig for mcp_security_framework
68
- mcp_permission_config = PermissionConfig(
69
- roles_file=permissions_config.get("roles_file"),
70
- default_role=permissions_config.get("default_role", "guest"),
71
- admin_role=permissions_config.get("admin_role", "admin"),
72
- role_hierarchy=permissions_config.get("role_hierarchy", {}),
73
- permission_cache_enabled=permissions_config.get(
74
- "permission_cache_enabled", True
75
- ),
76
- permission_cache_ttl=permissions_config.get(
77
- "permission_cache_ttl", 300
78
- ),
79
- wildcard_permissions=permissions_config.get(
80
- "wildcard_permissions", False
81
- ),
82
- strict_mode=permissions_config.get("strict_mode", True),
83
- roles=permissions_config.get("roles", {})
84
- )
85
-
86
- # Initialize PermissionManager first
87
- self.permission_manager = PermissionManager(mcp_permission_config)
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(
@@ -178,6 +178,7 @@ class Config:
178
178
  "hash_algorithm": "sha256",
179
179
  "crl_enabled": False,
180
180
  "crl_path": None,
181
+ "crl_url": None,
181
182
  "crl_validity_days": 30,
182
183
  "auto_renewal": False,
183
184
  "renewal_threshold_days": 30
@@ -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
- roles_file = permissions_section.get("roles_file")
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
- permission_config = PermissionConfig(
116
- enabled=permissions_section.get("enabled", True),
117
- roles_file=roles_file,
118
- default_role=permissions_section.get("default_role", "guest"),
119
- admin_role=permissions_section.get("admin_role", "admin"),
120
- role_hierarchy=permissions_section.get("role_hierarchy", {}),
121
- permission_cache_enabled=permissions_section.get("permission_cache_enabled", True),
122
- permission_cache_ttl=permissions_section.get("permission_cache_ttl", 300),
123
- wildcard_permissions=permissions_section.get("wildcard_permissions", False),
124
- strict_mode=permissions_section.get("strict_mode", True),
125
- roles=permissions_section.get("roles")
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
- return await self.certificate_manager.validate_certificate(cert_path)
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: MCP Proxy Adapter Team
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") -> ssl.SSLContext:
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
- return result.is_valid
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
@@ -107,6 +107,7 @@ class ConfigGenerator:
107
107
  "hash_algorithm": "sha256",
108
108
  "crl_enabled": False,
109
109
  "crl_path": None,
110
+ "crl_url": None,
110
111
  "crl_validity_days": 30,
111
112
  "auto_renewal": False,
112
113
  "renewal_threshold_days": 30
@@ -107,6 +107,7 @@ class ConfigGenerator:
107
107
  "hash_algorithm": "sha256",
108
108
  "crl_enabled": False,
109
109
  "crl_path": None,
110
+ "crl_url": None,
110
111
  "crl_validity_days": 30,
111
112
  "auto_renewal": False,
112
113
  "renewal_threshold_days": 30
@@ -2,5 +2,5 @@
2
2
  Version information for MCP Proxy Adapter.
3
3
  """
4
4
 
5
- __version__ = "6.2.34"
5
+ __version__ = "6.2.36"
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-proxy-adapter
3
- Version: 6.2.34
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=QIOV57A_hwDH2z_BVEBJKDsPpa3IUKlqhRAij4bpQik,21600
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=8Rv-tnRMFu7W1xVGrsKl9PqMpFzjts8hLs89BkGcJHw,76
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=gJX5WUMa-ByWpbvbFUftymH3BuQNaj1h1lGDqq9VU6g,8134
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=qDVmkRatuDeWylIPLjMq02Vpzff6DDTE_CstpzqGi7o,7773
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=6oJKVCL1CRnk3sTWX-PBzDjv737oe4QL-9r3l89kRkc,13715
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=_2mhpuoiRpSbUBifnQvtuziQfBRrXQUKtB58ALWTaqU,8220
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=4qruYxQ2kGLVOukLX2JOW5kslJ06RhkNqTobAgh4rfw,32801
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=2dxwBh9k_nUw9kgytZso5TNOQpBqd3c-RpKSTLoHlLE,46465
137
- mcp_proxy_adapter-6.2.34.dist-info/licenses/LICENSE,sha256=6KdtUcTwmTRbJrAmYjVn7e6S-V42ubeDJ-AiVEzZ510,1075
138
- mcp_proxy_adapter-6.2.34.dist-info/METADATA,sha256=l0uENuXxZKqEOdMXaaMBnEWWegRTQDO9CJYiNGWGIhA,22348
139
- mcp_proxy_adapter-6.2.34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
140
- mcp_proxy_adapter-6.2.34.dist-info/entry_points.txt,sha256=J3eV6ID0lt_VSp4lIdIgBFTqLCThgObNNxRCbyfiMHw,70
141
- mcp_proxy_adapter-6.2.34.dist-info/top_level.txt,sha256=JZT7vPLBYrtroX-ij68JBhJYbjDdghcV-DFySRy-Nnw,18
142
- mcp_proxy_adapter-6.2.34.dist-info/RECORD,,
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,,