mcp-proxy-adapter 6.4.43__py3-none-any.whl → 6.4.45__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. mcp_proxy_adapter/core/proxy_registration.py +100 -68
  2. mcp_proxy_adapter/examples/bugfix_certificate_config.py +284 -0
  3. mcp_proxy_adapter/examples/cert_manager_bugfix.py +203 -0
  4. mcp_proxy_adapter/examples/config_builder.py +574 -0
  5. mcp_proxy_adapter/examples/config_cli.py +283 -0
  6. mcp_proxy_adapter/examples/create_test_configs.py +169 -266
  7. mcp_proxy_adapter/examples/generate_certificates_bugfix.py +374 -0
  8. mcp_proxy_adapter/examples/generate_certificates_cli.py +406 -0
  9. mcp_proxy_adapter/examples/generate_certificates_fixed.py +313 -0
  10. mcp_proxy_adapter/examples/generate_certificates_framework.py +366 -0
  11. mcp_proxy_adapter/examples/generate_certificates_openssl.py +391 -0
  12. mcp_proxy_adapter/examples/required_certificates.py +210 -0
  13. mcp_proxy_adapter/examples/run_full_test_suite.py +117 -13
  14. mcp_proxy_adapter/examples/run_security_tests_fixed.py +41 -25
  15. mcp_proxy_adapter/examples/security_test_client.py +333 -86
  16. mcp_proxy_adapter/examples/test_config_builder.py +450 -0
  17. mcp_proxy_adapter/examples/update_config_certificates.py +136 -0
  18. mcp_proxy_adapter/version.py +1 -1
  19. {mcp_proxy_adapter-6.4.43.dist-info → mcp_proxy_adapter-6.4.45.dist-info}/METADATA +81 -1
  20. {mcp_proxy_adapter-6.4.43.dist-info → mcp_proxy_adapter-6.4.45.dist-info}/RECORD +23 -20
  21. mcp_proxy_adapter-6.4.45.dist-info/entry_points.txt +12 -0
  22. mcp_proxy_adapter/examples/create_certificates_simple.py +0 -661
  23. mcp_proxy_adapter/examples/generate_certificates.py +0 -192
  24. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +0 -515
  25. mcp_proxy_adapter/examples/generate_test_configs.py +0 -393
  26. mcp_proxy_adapter/examples/run_security_tests.py +0 -677
  27. mcp_proxy_adapter/examples/scripts/config_generator.py +0 -842
  28. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +0 -673
  29. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +0 -515
  30. mcp_proxy_adapter/examples/test_config_generator.py +0 -102
  31. mcp_proxy_adapter-6.4.43.dist-info/entry_points.txt +0 -2
  32. {mcp_proxy_adapter-6.4.43.dist-info → mcp_proxy_adapter-6.4.45.dist-info}/WHEEL +0 -0
  33. {mcp_proxy_adapter-6.4.43.dist-info → mcp_proxy_adapter-6.4.45.dist-info}/top_level.txt +0 -0
@@ -68,84 +68,116 @@ class ProxyRegistrationManager:
68
68
  except Exception:
69
69
  pass
70
70
 
71
- # Basic registration settings
72
- self.proxy_url = self.registration_config.get("proxy_url")
73
- if not self.proxy_url:
74
- raise ValueError(
75
- "proxy_url is required in registration configuration. "
76
- "Please specify a valid proxy URL in your configuration."
77
- )
78
- self.server_id = self.registration_config.get("server_id")
79
- if not self.server_id:
80
- # Try to get from proxy_info.name as fallback
81
- self.server_id = self.registration_config.get("proxy_info", {}).get("name")
82
- if not self.server_id:
71
+ # Check if registration is enabled
72
+ self.enabled = self.registration_config.get("enabled", False)
73
+
74
+ # Basic registration settings - only validate if enabled
75
+ if self.enabled:
76
+ self.proxy_url = self.registration_config.get("proxy_url")
77
+ if not self.proxy_url:
83
78
  raise ValueError(
84
- "server_id is required in registration configuration. "
85
- "Please specify a server_id or proxy_info.name in your configuration."
79
+ "proxy_url is required in registration configuration. "
80
+ "Please specify a valid proxy URL in your configuration."
86
81
  )
87
- self.server_name = self.registration_config.get("server_name")
88
- if not self.server_name:
89
- # Try to get from proxy_info.name as fallback
90
- self.server_name = self.registration_config.get("proxy_info", {}).get("name")
82
+ else:
83
+ self.proxy_url = None
84
+
85
+ if self.enabled:
86
+ self.server_id = self.registration_config.get("server_id")
87
+ if not self.server_id:
88
+ # Try to get from proxy_info.name as fallback
89
+ self.server_id = self.registration_config.get("proxy_info", {}).get("name")
90
+ if not self.server_id:
91
+ raise ValueError(
92
+ "server_id is required in registration configuration. "
93
+ "Please specify a server_id or proxy_info.name in your configuration."
94
+ )
95
+ self.server_name = self.registration_config.get("server_name")
91
96
  if not self.server_name:
97
+ # Try to get from proxy_info.name as fallback
98
+ self.server_name = self.registration_config.get("proxy_info", {}).get("name")
99
+ if not self.server_name:
100
+ raise ValueError(
101
+ "server_name is required in registration configuration. "
102
+ "Please specify a server_name or proxy_info.name in your configuration."
103
+ )
104
+ self.description = self.registration_config.get("description")
105
+ if not self.description:
106
+ # Try to get from proxy_info.description as fallback
107
+ self.description = self.registration_config.get("proxy_info", {}).get("description")
108
+ if not self.description:
109
+ raise ValueError(
110
+ "description is required in registration configuration. "
111
+ "Please specify a description or proxy_info.description in your configuration."
112
+ )
113
+ else:
114
+ self.server_id = None
115
+ self.server_name = None
116
+ self.description = None
117
+
118
+ if self.enabled:
119
+ self.version = self.registration_config.get("version")
120
+ if not self.version:
121
+ # Try to get from proxy_info.version as fallback
122
+ self.version = self.registration_config.get("proxy_info", {}).get("version")
123
+ if not self.version:
124
+ raise ValueError(
125
+ "version is required in registration configuration. "
126
+ "Please specify a version or proxy_info.version in your configuration."
127
+ )
128
+ else:
129
+ self.version = None
130
+
131
+ # Heartbeat settings - only validate if enabled
132
+ if self.enabled:
133
+ heartbeat_config = self.registration_config.get("heartbeat", {})
134
+ self.timeout = heartbeat_config.get("timeout")
135
+ if self.timeout is None:
92
136
  raise ValueError(
93
- "server_name is required in registration configuration. "
94
- "Please specify a server_name or proxy_info.name in your configuration."
137
+ "heartbeat.timeout is required in registration configuration. "
138
+ "Please specify a timeout value."
95
139
  )
96
- self.description = self.registration_config.get("description")
97
- if not self.description:
98
- # Try to get from proxy_info.description as fallback
99
- self.description = self.registration_config.get("proxy_info", {}).get("description")
100
- if not self.description:
140
+ self.retry_attempts = heartbeat_config.get("retry_attempts")
141
+ if self.retry_attempts is None:
101
142
  raise ValueError(
102
- "description is required in registration configuration. "
103
- "Please specify a description or proxy_info.description in your configuration."
143
+ "heartbeat.retry_attempts is required in registration configuration. "
144
+ "Please specify a retry_attempts value."
104
145
  )
105
- self.version = self.registration_config.get("version")
106
- if not self.version:
107
- # Try to get from proxy_info.version as fallback
108
- self.version = self.registration_config.get("proxy_info", {}).get("version")
109
- if not self.version:
146
+ else:
147
+ self.timeout = None
148
+ self.retry_attempts = None
149
+
150
+ if self.enabled:
151
+ self.retry_delay = heartbeat_config.get("retry_delay")
152
+ if self.retry_delay is None:
110
153
  raise ValueError(
111
- "version is required in registration configuration. "
112
- "Please specify a version or proxy_info.version in your configuration."
154
+ "heartbeat.retry_delay is required in registration configuration. "
155
+ "Please specify a retry_delay value."
113
156
  )
114
-
115
- # Heartbeat settings
116
- heartbeat_config = self.registration_config.get("heartbeat", {})
117
- self.timeout = heartbeat_config.get("timeout")
118
- if self.timeout is None:
119
- raise ValueError(
120
- "heartbeat.timeout is required in registration configuration. "
121
- "Please specify a timeout value."
122
- )
123
- self.retry_attempts = heartbeat_config.get("retry_attempts")
124
- if self.retry_attempts is None:
125
- raise ValueError(
126
- "heartbeat.retry_attempts is required in registration configuration. "
127
- "Please specify a retry_attempts value."
128
- )
129
- self.retry_delay = heartbeat_config.get("retry_delay")
130
- if self.retry_delay is None:
131
- raise ValueError(
132
- "heartbeat.retry_delay is required in registration configuration. "
133
- "Please specify a retry_delay value."
134
- )
135
- self.heartbeat_interval = heartbeat_config.get("interval")
136
- if self.heartbeat_interval is None:
137
- raise ValueError(
138
- "heartbeat.interval is required in registration configuration. "
139
- "Please specify an interval value."
140
- )
157
+ else:
158
+ self.retry_delay = None
159
+
160
+ if self.enabled:
161
+ self.heartbeat_interval = heartbeat_config.get("interval")
162
+ if self.heartbeat_interval is None:
163
+ raise ValueError(
164
+ "heartbeat.interval is required in registration configuration. "
165
+ "Please specify an interval value."
166
+ )
167
+ else:
168
+ self.heartbeat_interval = None
141
169
 
142
170
  # Auto registration settings
143
- self.auto_register = self.registration_config.get("enabled")
144
- if self.auto_register is None:
145
- raise ValueError(
146
- "enabled is required in registration configuration. "
147
- "Please specify whether registration is enabled (true/false)."
148
- )
171
+ if self.enabled:
172
+ self.auto_register = self.registration_config.get("enabled")
173
+ if self.auto_register is None:
174
+ raise ValueError(
175
+ "enabled is required in registration configuration. "
176
+ "Please specify whether registration is enabled (true/false)."
177
+ )
178
+ else:
179
+ self.auto_register = False
180
+
149
181
  self.auto_unregister = True # Always unregister on shutdown
150
182
 
151
183
  # Initialize client security manager
@@ -168,7 +200,7 @@ class ProxyRegistrationManager:
168
200
  Returns:
169
201
  True if registration is enabled, False otherwise.
170
202
  """
171
- return self.registration_config.get("enabled", False)
203
+ return self.enabled
172
204
 
173
205
  def set_server_url(self, server_url: str) -> None:
174
206
  """
@@ -0,0 +1,284 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Bugfix for mcp_security_framework CertificateConfig
4
+ This script provides a fix for the CertificateConfig validation issue.
5
+
6
+ ISSUE: CertificateConfig requires CA certificate and key paths even when creating a CA certificate.
7
+ This creates a chicken-and-egg problem where you need a CA to create a CA.
8
+
9
+ SOLUTION: Modify the validation logic to allow CA creation mode.
10
+
11
+ Author: Vasiliy Zdanovskiy
12
+ email: vasilyvz@gmail.com
13
+ """
14
+
15
+ import sys
16
+ from pathlib import Path
17
+ from typing import Optional
18
+ from pydantic import BaseModel, Field, field_validator, model_validator
19
+
20
+
21
+ class FixedCertificateConfig(BaseModel):
22
+ """
23
+ Fixed Certificate Management Configuration Model
24
+
25
+ This model defines certificate management configuration settings
26
+ including CA settings, certificate storage, and validation options.
27
+
28
+ BUGFIX: Added ca_creation_mode to allow CA certificate creation
29
+ without requiring existing CA paths.
30
+
31
+ Attributes:
32
+ enabled: Whether certificate management is enabled
33
+ ca_creation_mode: Whether we are in CA creation mode (bypasses CA path validation)
34
+ ca_cert_path: Path to CA certificate
35
+ ca_key_path: Path to CA private key
36
+ cert_storage_path: Path for certificate storage
37
+ key_storage_path: Path for private key storage
38
+ default_validity_days: Default certificate validity in days
39
+ key_size: RSA key size for generated certificates
40
+ hash_algorithm: Hash algorithm for certificate signing
41
+ crl_enabled: Whether CRL is enabled
42
+ crl_path: Path for CRL storage
43
+ crl_validity_days: CRL validity period in days
44
+ auto_renewal: Whether automatic certificate renewal is enabled
45
+ renewal_threshold_days: Days before expiry to renew
46
+ """
47
+
48
+ enabled: bool = Field(
49
+ default=False, description="Whether certificate management is enabled"
50
+ )
51
+ ca_creation_mode: bool = Field(
52
+ default=False, description="Whether we are in CA creation mode (bypasses CA path validation)"
53
+ )
54
+ ca_cert_path: Optional[str] = Field(
55
+ default=None, description="Path to CA certificate"
56
+ )
57
+ ca_key_path: Optional[str] = Field(
58
+ default=None, description="Path to CA private key"
59
+ )
60
+ cert_storage_path: str = Field(
61
+ default="./certs", description="Path for certificate storage"
62
+ )
63
+ key_storage_path: str = Field(
64
+ default="./keys", description="Path for private key storage"
65
+ )
66
+ default_validity_days: int = Field(
67
+ default=365, ge=1, le=3650, description="Default certificate validity in days"
68
+ )
69
+ key_size: int = Field(
70
+ default=2048,
71
+ ge=1024,
72
+ le=4096,
73
+ description="RSA key size for generated certificates",
74
+ )
75
+ hash_algorithm: str = Field(
76
+ default="sha256", description="Hash algorithm for certificate signing"
77
+ )
78
+ crl_enabled: bool = Field(default=False, description="Whether CRL is enabled")
79
+ crl_path: Optional[str] = Field(default=None, description="Path for CRL storage")
80
+ crl_validity_days: int = Field(
81
+ default=30, ge=1, le=365, description="CRL validity period in days"
82
+ )
83
+ auto_renewal: bool = Field(
84
+ default=False, description="Whether automatic certificate renewal is enabled"
85
+ )
86
+ renewal_threshold_days: int = Field(
87
+ default=30, ge=1, le=90, description="Days before expiry to renew"
88
+ )
89
+
90
+ @field_validator("hash_algorithm")
91
+ @classmethod
92
+ def validate_hash_algorithm(cls, v):
93
+ """Validate hash algorithm."""
94
+ valid_algorithms = ["sha1", "sha256", "sha384", "sha512"]
95
+ if v not in valid_algorithms:
96
+ raise ValueError(
97
+ f"Invalid hash algorithm. Must be one of: {valid_algorithms}"
98
+ )
99
+ return v
100
+
101
+ @model_validator(mode="after")
102
+ def validate_certificate_configuration(self):
103
+ """Validate certificate configuration consistency."""
104
+ if self.enabled:
105
+ # BUGFIX: Only require CA paths if not in CA creation mode
106
+ if not self.ca_creation_mode:
107
+ if not self.ca_cert_path or not self.ca_key_path:
108
+ raise ValueError(
109
+ "Certificate management enabled but CA certificate and key paths are required. "
110
+ "Set ca_creation_mode=True if you are creating a CA certificate."
111
+ )
112
+
113
+ if self.crl_enabled and not self.crl_path:
114
+ raise ValueError("CRL enabled but CRL path is required")
115
+
116
+ return self
117
+
118
+
119
+ def create_patch_file():
120
+ """Create a patch file for the mcp_security_framework."""
121
+
122
+ patch_content = '''--- a/mcp_security_framework/schemas/config.py
123
+ +++ b/mcp_security_framework/schemas/config.py
124
+ @@ -1,6 +1,7 @@
125
+ from typing import Optional
126
+ from pydantic import BaseModel, Field, field_validator, model_validator
127
+
128
+ +
129
+ class CertificateConfig(BaseModel):
130
+ """
131
+ Certificate Management Configuration Model
132
+ @@ -8,6 +9,7 @@ class CertificateConfig(BaseModel):
133
+ This model defines certificate management configuration settings
134
+ including CA settings, certificate storage, and validation options.
135
+
136
+ + BUGFIX: Added ca_creation_mode to allow CA certificate creation
137
+ Attributes:
138
+ enabled: Whether certificate management is enabled
139
+ ca_cert_path: Path to CA certificate
140
+ @@ -20,6 +22,9 @@ class CertificateConfig(BaseModel):
141
+ enabled: bool = Field(
142
+ default=False, description="Whether certificate management is enabled"
143
+ )
144
+ + ca_creation_mode: bool = Field(
145
+ + default=False, description="Whether we are in CA creation mode (bypasses CA path validation)"
146
+ + )
147
+ ca_cert_path: Optional[str] = Field(
148
+ default=None, description="Path to CA certificate"
149
+ )
150
+ @@ -60,7 +65,9 @@ class CertificateConfig(BaseModel):
151
+ @model_validator(mode="after")
152
+ def validate_certificate_configuration(self):
153
+ """Validate certificate configuration consistency."""
154
+ if self.enabled:
155
+ - if not self.ca_cert_path or not self.ca_key_path:
156
+ - raise ValueError(
157
+ - "Certificate management enabled but CA certificate and key paths are required"
158
+ - )
159
+ + # BUGFIX: Only require CA paths if not in CA creation mode
160
+ + if not self.ca_creation_mode:
161
+ + if not self.ca_cert_path or not self.ca_key_path:
162
+ + raise ValueError(
163
+ + "Certificate management enabled but CA certificate and key paths are required. "
164
+ + "Set ca_creation_mode=True if you are creating a CA certificate."
165
+ + )
166
+
167
+ if self.crl_enabled and not self.crl_path:
168
+ raise ValueError("CRL enabled but CRL path is required")
169
+
170
+ return self
171
+ '''
172
+
173
+ patch_file = Path("certificate_config_bugfix.patch")
174
+ with open(patch_file, 'w') as f:
175
+ f.write(patch_content)
176
+
177
+ print(f"✅ Patch file created: {patch_file}")
178
+ return patch_file
179
+
180
+
181
+ def test_fixed_config():
182
+ """Test the fixed configuration."""
183
+ print("🧪 Testing Fixed CertificateConfig")
184
+ print("=" * 50)
185
+
186
+ # Test 1: CA creation mode should work without CA paths
187
+ print("Test 1: CA creation mode")
188
+ try:
189
+ config1 = FixedCertificateConfig(
190
+ enabled=True,
191
+ ca_creation_mode=True,
192
+ cert_storage_path="./certs",
193
+ key_storage_path="./keys"
194
+ )
195
+ print("✅ CA creation mode works correctly")
196
+ except Exception as e:
197
+ print(f"❌ CA creation mode failed: {e}")
198
+ return False
199
+
200
+ # Test 2: Normal mode should require CA paths
201
+ print("Test 2: Normal mode without CA paths")
202
+ try:
203
+ config2 = FixedCertificateConfig(
204
+ enabled=True,
205
+ ca_creation_mode=False,
206
+ cert_storage_path="./certs",
207
+ key_storage_path="./keys"
208
+ )
209
+ print("❌ Normal mode should have failed without CA paths")
210
+ return False
211
+ except ValueError as e:
212
+ print("✅ Normal mode correctly requires CA paths")
213
+ except Exception as e:
214
+ print(f"❌ Unexpected error: {e}")
215
+ return False
216
+
217
+ # Test 3: Normal mode with CA paths should work
218
+ print("Test 3: Normal mode with CA paths")
219
+ try:
220
+ config3 = FixedCertificateConfig(
221
+ enabled=True,
222
+ ca_creation_mode=False,
223
+ ca_cert_path="./certs/ca.crt",
224
+ ca_key_path="./keys/ca.key",
225
+ cert_storage_path="./certs",
226
+ key_storage_path="./keys"
227
+ )
228
+ print("✅ Normal mode with CA paths works correctly")
229
+ except Exception as e:
230
+ print(f"❌ Normal mode with CA paths failed: {e}")
231
+ return False
232
+
233
+ print("\n🎉 All tests passed!")
234
+ return True
235
+
236
+
237
+ def main():
238
+ """Main entry point."""
239
+ print("🔧 mcp_security_framework CertificateConfig Bugfix")
240
+ print("=" * 60)
241
+ print()
242
+ print("ISSUE DESCRIPTION:")
243
+ print("CertificateConfig requires CA certificate and key paths even when")
244
+ print("creating a CA certificate. This creates a chicken-and-egg problem.")
245
+ print()
246
+ print("SOLUTION:")
247
+ print("Add ca_creation_mode field to bypass CA path validation when")
248
+ print("creating a CA certificate.")
249
+ print()
250
+
251
+ # Test the fix
252
+ if test_fixed_config():
253
+ # Create patch file
254
+ patch_file = create_patch_file()
255
+ print(f"\n📁 To apply this fix to mcp_security_framework:")
256
+ print(f" 1. Copy the patch file to the framework directory")
257
+ print(f" 2. Run: patch -p1 < {patch_file}")
258
+ print(f" 3. Or manually apply the changes shown in the patch")
259
+ print()
260
+ print("🔧 USAGE EXAMPLE:")
261
+ print(" # For CA creation:")
262
+ print(" config = CertificateConfig(")
263
+ print(" enabled=True,")
264
+ print(" ca_creation_mode=True, # <-- This is the fix")
265
+ print(" cert_storage_path='./certs',")
266
+ print(" key_storage_path='./keys'")
267
+ print(" )")
268
+ print()
269
+ print(" # For normal certificate creation:")
270
+ print(" config = CertificateConfig(")
271
+ print(" enabled=True,")
272
+ print(" ca_creation_mode=False,")
273
+ print(" ca_cert_path='./certs/ca.crt',")
274
+ print(" ca_key_path='./keys/ca.key'")
275
+ print(" )")
276
+ else:
277
+ print("❌ Tests failed!")
278
+ return 1
279
+
280
+ return 0
281
+
282
+
283
+ if __name__ == "__main__":
284
+ sys.exit(main())
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Bugfix for mcp_security_framework CertificateManager
4
+ This script provides a fix for the CertificateManager validation issue.
5
+
6
+ ISSUE: CertificateManager._validate_configuration() doesn't check ca_creation_mode
7
+ and always requires CA certificate and key paths, even when creating a CA.
8
+
9
+ SOLUTION: Modify the validation logic to skip CA path validation in CA creation mode.
10
+
11
+ Author: Vasiliy Zdanovskiy
12
+ email: vasilyvz@gmail.com
13
+ """
14
+
15
+ import sys
16
+ from pathlib import Path
17
+
18
+
19
+ def create_cert_manager_patch():
20
+ """Create a patch file for the CertificateManager."""
21
+
22
+ patch_content = '''--- a/mcp_security_framework/core/cert_manager.py
23
+ +++ b/mcp_security_framework/core/cert_manager.py
24
+ @@ -1,6 +1,7 @@
25
+ import logging
26
+ import os
27
+ from typing import Dict, List, Optional, Union
28
+ +from pathlib import Path
29
+
30
+ from cryptography import x509
31
+ from cryptography.hazmat.primitives import hashes, serialization
32
+ @@ -50,6 +51,7 @@ class CertificateManager:
33
+ """
34
+ self.config = config
35
+ self.logger = logging.getLogger(__name__)
36
+ + self._certificate_cache: Dict[str, CertificateInfo] = {}
37
+ + self._crl_cache: Dict[str, x509.CertificateRevocationList] = {}
38
+
39
+ # Validate configuration
40
+ self._validate_configuration()
41
+ @@ -70,7 +72,7 @@ class CertificateManager:
42
+ def _validate_configuration(self) -> None:
43
+ """Validate certificate configuration."""
44
+ # Skip validation if certificate management is disabled
45
+ - if not self.config.enabled:
46
+ + if not self.config.enabled:
47
+ return
48
+
49
+ - if not self.config.ca_cert_path:
50
+ - raise CertificateConfigurationError("CA certificate path is required")
51
+ -
52
+ - if not self.config.ca_key_path:
53
+ - raise CertificateConfigurationError("CA private key path is required")
54
+ -
55
+ - if not os.path.exists(self.config.ca_cert_path):
56
+ - raise CertificateConfigurationError(
57
+ - f"CA certificate file not found: {self.config.ca_cert_path}"
58
+ - )
59
+ -
60
+ - if not os.path.exists(self.config.ca_key_path):
61
+ - raise CertificateConfigurationError(
62
+ - f"CA private key file not found: {self.config.ca_key_path}"
63
+ - )
64
+ + # BUGFIX: Skip CA path validation if in CA creation mode
65
+ + if self.config.ca_creation_mode:
66
+ + self.logger.info("CA creation mode enabled, skipping CA path validation")
67
+ + return
68
+ +
69
+ + if not self.config.ca_cert_path:
70
+ + raise CertificateConfigurationError("CA certificate path is required")
71
+ +
72
+ + if not self.config.ca_key_path:
73
+ + raise CertificateConfigurationError("CA private key path is required")
74
+ +
75
+ + if not os.path.exists(self.config.ca_cert_path):
76
+ + raise CertificateConfigurationError(
77
+ + f"CA certificate file not found: {self.config.ca_cert_path}"
78
+ + )
79
+ +
80
+ + if not os.path.exists(self.config.ca_key_path):
81
+ + raise CertificateConfigurationError(
82
+ + f"CA private key file not found: {self.config.ca_key_path}"
83
+ + )
84
+ '''
85
+
86
+ patch_file = Path("cert_manager_bugfix.patch")
87
+ with open(patch_file, 'w') as f:
88
+ f.write(patch_content)
89
+
90
+ print(f"✅ CertificateManager patch file created: {patch_file}")
91
+ return patch_file
92
+
93
+
94
+ def test_fixed_cert_manager():
95
+ """Test the fixed CertificateManager."""
96
+ print("🧪 Testing Fixed CertificateManager")
97
+ print("=" * 50)
98
+
99
+ # Create a mock fixed CertificateManager class
100
+ class FixedCertificateManager:
101
+ def __init__(self, config):
102
+ self.config = config
103
+ self.logger = type('Logger', (), {'info': lambda self, msg, **kwargs: print(f"INFO: {msg}")})()
104
+ self._validate_configuration()
105
+
106
+ def _validate_configuration(self):
107
+ """Fixed validation logic."""
108
+ if not self.config.enabled:
109
+ return
110
+
111
+ # BUGFIX: Skip CA path validation if in CA creation mode
112
+ if self.config.ca_creation_mode:
113
+ self.logger.info("CA creation mode enabled, skipping CA path validation")
114
+ return
115
+
116
+ if not self.config.ca_cert_path:
117
+ raise ValueError("CA certificate path is required")
118
+
119
+ if not self.config.ca_key_path:
120
+ raise ValueError("CA private key path is required")
121
+
122
+ # Test 1: CA creation mode should work without CA paths
123
+ print("Test 1: CA creation mode")
124
+ try:
125
+ from mcp_security_framework.schemas.config import CertificateConfig
126
+
127
+ config1 = CertificateConfig(
128
+ enabled=True,
129
+ ca_creation_mode=True,
130
+ cert_storage_path="./certs",
131
+ key_storage_path="./keys"
132
+ )
133
+
134
+ manager1 = FixedCertificateManager(config1)
135
+ print("✅ CA creation mode works correctly")
136
+ except Exception as e:
137
+ print(f"❌ CA creation mode failed: {e}")
138
+ return False
139
+
140
+ # Test 2: Normal mode should require CA paths
141
+ print("Test 2: Normal mode without CA paths")
142
+ try:
143
+ config2 = CertificateConfig(
144
+ enabled=True,
145
+ ca_creation_mode=False,
146
+ cert_storage_path="./certs",
147
+ key_storage_path="./keys"
148
+ )
149
+
150
+ manager2 = FixedCertificateManager(config2)
151
+ print("❌ Normal mode should have failed without CA paths")
152
+ return False
153
+ except ValueError as e:
154
+ print("✅ Normal mode correctly requires CA paths")
155
+ except Exception as e:
156
+ print(f"❌ Unexpected error: {e}")
157
+ return False
158
+
159
+ print("\n🎉 All tests passed!")
160
+ return True
161
+
162
+
163
+ def main():
164
+ """Main entry point."""
165
+ print("🔧 mcp_security_framework CertificateManager Bugfix")
166
+ print("=" * 60)
167
+ print()
168
+ print("ISSUE DESCRIPTION:")
169
+ print("CertificateManager._validate_configuration() doesn't check ca_creation_mode")
170
+ print("and always requires CA certificate and key paths, even when creating a CA.")
171
+ print()
172
+ print("SOLUTION:")
173
+ print("Modify _validate_configuration() to skip CA path validation when")
174
+ print("ca_creation_mode=True.")
175
+ print()
176
+
177
+ # Test the fix
178
+ if test_fixed_cert_manager():
179
+ # Create patch file
180
+ patch_file = create_cert_manager_patch()
181
+ print(f"\n📁 To apply this fix to mcp_security_framework:")
182
+ print(f" 1. Copy the patch file to the framework directory")
183
+ print(f" 2. Run: patch -p1 < {patch_file}")
184
+ print(f" 3. Or manually apply the changes shown in the patch")
185
+ print()
186
+ print("🔧 USAGE EXAMPLE:")
187
+ print(" # For CA creation:")
188
+ print(" config = CertificateConfig(")
189
+ print(" enabled=True,")
190
+ print(" ca_creation_mode=True, # <-- This bypasses CA path validation")
191
+ print(" cert_storage_path='./certs',")
192
+ print(" key_storage_path='./keys'")
193
+ print(" )")
194
+ print(" manager = CertificateManager(config) # <-- This will now work")
195
+ else:
196
+ print("❌ Tests failed!")
197
+ return 1
198
+
199
+ return 0
200
+
201
+
202
+ if __name__ == "__main__":
203
+ sys.exit(main())