mcp-security-framework 0.1.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_security_framework/__init__.py +96 -0
- mcp_security_framework/cli/__init__.py +18 -0
- mcp_security_framework/cli/cert_cli.py +511 -0
- mcp_security_framework/cli/security_cli.py +791 -0
- mcp_security_framework/constants.py +209 -0
- mcp_security_framework/core/__init__.py +61 -0
- mcp_security_framework/core/auth_manager.py +1011 -0
- mcp_security_framework/core/cert_manager.py +1663 -0
- mcp_security_framework/core/permission_manager.py +735 -0
- mcp_security_framework/core/rate_limiter.py +602 -0
- mcp_security_framework/core/security_manager.py +943 -0
- mcp_security_framework/core/ssl_manager.py +735 -0
- mcp_security_framework/examples/__init__.py +75 -0
- mcp_security_framework/examples/django_example.py +615 -0
- mcp_security_framework/examples/fastapi_example.py +472 -0
- mcp_security_framework/examples/flask_example.py +506 -0
- mcp_security_framework/examples/gateway_example.py +803 -0
- mcp_security_framework/examples/microservice_example.py +690 -0
- mcp_security_framework/examples/standalone_example.py +576 -0
- mcp_security_framework/middleware/__init__.py +250 -0
- mcp_security_framework/middleware/auth_middleware.py +292 -0
- mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
- mcp_security_framework/middleware/fastapi_middleware.py +757 -0
- mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
- mcp_security_framework/middleware/flask_middleware.py +591 -0
- mcp_security_framework/middleware/mtls_middleware.py +439 -0
- mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
- mcp_security_framework/middleware/security_middleware.py +507 -0
- mcp_security_framework/schemas/__init__.py +109 -0
- mcp_security_framework/schemas/config.py +694 -0
- mcp_security_framework/schemas/models.py +709 -0
- mcp_security_framework/schemas/responses.py +686 -0
- mcp_security_framework/tests/__init__.py +0 -0
- mcp_security_framework/utils/__init__.py +121 -0
- mcp_security_framework/utils/cert_utils.py +525 -0
- mcp_security_framework/utils/crypto_utils.py +475 -0
- mcp_security_framework/utils/validation_utils.py +571 -0
- mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
- mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
- mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
- mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_cli/__init__.py +0 -0
- tests/test_cli/test_cert_cli.py +379 -0
- tests/test_cli/test_security_cli.py +657 -0
- tests/test_core/__init__.py +0 -0
- tests/test_core/test_auth_manager.py +582 -0
- tests/test_core/test_cert_manager.py +795 -0
- tests/test_core/test_permission_manager.py +395 -0
- tests/test_core/test_rate_limiter.py +626 -0
- tests/test_core/test_security_manager.py +841 -0
- tests/test_core/test_ssl_manager.py +532 -0
- tests/test_examples/__init__.py +8 -0
- tests/test_examples/test_fastapi_example.py +264 -0
- tests/test_examples/test_flask_example.py +238 -0
- tests/test_examples/test_standalone_example.py +292 -0
- tests/test_integration/__init__.py +0 -0
- tests/test_integration/test_auth_flow.py +502 -0
- tests/test_integration/test_certificate_flow.py +527 -0
- tests/test_integration/test_fastapi_integration.py +341 -0
- tests/test_integration/test_flask_integration.py +398 -0
- tests/test_integration/test_standalone_integration.py +493 -0
- tests/test_middleware/__init__.py +0 -0
- tests/test_middleware/test_fastapi_middleware.py +523 -0
- tests/test_middleware/test_flask_middleware.py +582 -0
- tests/test_middleware/test_security_middleware.py +493 -0
- tests/test_schemas/__init__.py +0 -0
- tests/test_schemas/test_config.py +811 -0
- tests/test_schemas/test_models.py +879 -0
- tests/test_schemas/test_responses.py +1054 -0
- tests/test_schemas/test_serialization.py +493 -0
- tests/test_utils/__init__.py +0 -0
- tests/test_utils/test_cert_utils.py +510 -0
- tests/test_utils/test_crypto_utils.py +603 -0
- tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,791 @@
|
|
1
|
+
"""
|
2
|
+
Security Management CLI Module
|
3
|
+
|
4
|
+
This module provides command-line interface for security management
|
5
|
+
operations including authentication, authorization, and security configuration.
|
6
|
+
|
7
|
+
Key Features:
|
8
|
+
- API key management
|
9
|
+
- User authentication testing
|
10
|
+
- Permission validation
|
11
|
+
- Security configuration management
|
12
|
+
- Rate limiting management
|
13
|
+
- Security status monitoring
|
14
|
+
|
15
|
+
Commands:
|
16
|
+
auth: Authentication operations
|
17
|
+
permissions: Permission management
|
18
|
+
rate-limit: Rate limiting operations
|
19
|
+
config: Configuration management
|
20
|
+
status: Security status monitoring
|
21
|
+
|
22
|
+
Author: MCP Security Team
|
23
|
+
Version: 1.0.0
|
24
|
+
License: MIT
|
25
|
+
"""
|
26
|
+
|
27
|
+
import click
|
28
|
+
import json
|
29
|
+
import os
|
30
|
+
from pathlib import Path
|
31
|
+
from typing import Optional, List
|
32
|
+
from datetime import datetime
|
33
|
+
|
34
|
+
from ..core.security_manager import SecurityManager
|
35
|
+
from ..schemas.config import SecurityConfig, AuthConfig, RateLimitConfig, SSLConfig, PermissionConfig
|
36
|
+
from ..schemas.models import AuthResult, AuthStatus
|
37
|
+
|
38
|
+
|
39
|
+
@click.group()
|
40
|
+
@click.option('--config', '-c', 'config_path',
|
41
|
+
help='Path to security configuration file')
|
42
|
+
@click.option('--verbose', '-v', is_flag=True,
|
43
|
+
help='Enable verbose output')
|
44
|
+
@click.pass_context
|
45
|
+
def security_cli(ctx, config_path: Optional[str], verbose: bool):
|
46
|
+
"""
|
47
|
+
Security Management CLI
|
48
|
+
|
49
|
+
Manage security operations including authentication, authorization,
|
50
|
+
and security configuration.
|
51
|
+
"""
|
52
|
+
ctx.ensure_object(dict)
|
53
|
+
ctx.obj['verbose'] = verbose
|
54
|
+
|
55
|
+
# Load configuration
|
56
|
+
if config_path and os.path.exists(config_path):
|
57
|
+
with open(config_path, 'r') as f:
|
58
|
+
config_data = json.load(f)
|
59
|
+
ctx.obj['config'] = SecurityConfig(**config_data)
|
60
|
+
else:
|
61
|
+
# Use default configuration
|
62
|
+
ctx.obj['config'] = SecurityConfig(
|
63
|
+
auth=AuthConfig(enabled=True),
|
64
|
+
rate_limit=RateLimitConfig(enabled=True),
|
65
|
+
ssl=SSLConfig(enabled=False),
|
66
|
+
permissions=PermissionConfig(enabled=True)
|
67
|
+
)
|
68
|
+
|
69
|
+
# Create security manager
|
70
|
+
ctx.obj['security_manager'] = SecurityManager(ctx.obj['config'])
|
71
|
+
|
72
|
+
|
73
|
+
@security_cli.group()
|
74
|
+
@click.pass_context
|
75
|
+
def auth(ctx):
|
76
|
+
"""
|
77
|
+
Authentication operations.
|
78
|
+
|
79
|
+
Manage authentication including API keys, user authentication,
|
80
|
+
and authentication testing.
|
81
|
+
"""
|
82
|
+
pass
|
83
|
+
|
84
|
+
|
85
|
+
@auth.command()
|
86
|
+
@click.option('--username', '-u', required=True,
|
87
|
+
help='Username for the API key')
|
88
|
+
@click.option('--api-key', '-k', required=True,
|
89
|
+
help='API key value')
|
90
|
+
@click.pass_context
|
91
|
+
def add_api_key(ctx, username: str, api_key: str):
|
92
|
+
"""
|
93
|
+
Add an API key for a user.
|
94
|
+
|
95
|
+
This command adds an API key to the authentication system
|
96
|
+
for the specified user.
|
97
|
+
"""
|
98
|
+
try:
|
99
|
+
security_manager = ctx.obj['security_manager']
|
100
|
+
verbose = ctx.obj['verbose']
|
101
|
+
|
102
|
+
if verbose:
|
103
|
+
click.echo(f"Adding API key for user: {username}")
|
104
|
+
|
105
|
+
# Add API key
|
106
|
+
success = security_manager.auth_manager.add_api_key(username, api_key)
|
107
|
+
|
108
|
+
if success:
|
109
|
+
click.echo(f"✅ API key added successfully for user: {username}")
|
110
|
+
else:
|
111
|
+
click.echo(f"❌ Failed to add API key for user: {username}", err=True)
|
112
|
+
raise click.Abort()
|
113
|
+
|
114
|
+
except Exception as e:
|
115
|
+
click.echo(f"❌ Failed to add API key: {str(e)}", err=True)
|
116
|
+
raise click.Abort()
|
117
|
+
|
118
|
+
|
119
|
+
@auth.command()
|
120
|
+
@click.option('--username', '-u', required=True,
|
121
|
+
help='Username to remove API key for')
|
122
|
+
@click.pass_context
|
123
|
+
def remove_api_key(ctx, username: str):
|
124
|
+
"""
|
125
|
+
Remove an API key for a user.
|
126
|
+
|
127
|
+
This command removes an API key from the authentication system
|
128
|
+
for the specified user.
|
129
|
+
"""
|
130
|
+
try:
|
131
|
+
security_manager = ctx.obj['security_manager']
|
132
|
+
verbose = ctx.obj['verbose']
|
133
|
+
|
134
|
+
if verbose:
|
135
|
+
click.echo(f"Removing API key for user: {username}")
|
136
|
+
|
137
|
+
# Remove API key
|
138
|
+
success = security_manager.auth_manager.remove_api_key(username)
|
139
|
+
|
140
|
+
if success:
|
141
|
+
click.echo(f"✅ API key removed successfully for user: {username}")
|
142
|
+
else:
|
143
|
+
click.echo(f"❌ Failed to remove API key for user: {username}", err=True)
|
144
|
+
raise click.Abort()
|
145
|
+
|
146
|
+
except Exception as e:
|
147
|
+
click.echo(f"❌ Failed to remove API key: {str(e)}", err=True)
|
148
|
+
raise click.Abort()
|
149
|
+
|
150
|
+
|
151
|
+
@auth.command()
|
152
|
+
@click.option('--api-key', '-k', required=True,
|
153
|
+
help='API key to test')
|
154
|
+
@click.pass_context
|
155
|
+
def test_api_key(ctx, api_key: str):
|
156
|
+
"""
|
157
|
+
Test API key authentication.
|
158
|
+
|
159
|
+
This command tests if an API key is valid and returns
|
160
|
+
authentication information.
|
161
|
+
"""
|
162
|
+
try:
|
163
|
+
security_manager = ctx.obj['security_manager']
|
164
|
+
verbose = ctx.obj['verbose']
|
165
|
+
|
166
|
+
if verbose:
|
167
|
+
click.echo(f"Testing API key authentication")
|
168
|
+
|
169
|
+
# Test API key
|
170
|
+
auth_result = security_manager.auth_manager.authenticate_api_key(api_key)
|
171
|
+
|
172
|
+
if auth_result.is_valid:
|
173
|
+
click.echo(f"✅ API key authentication successful!")
|
174
|
+
click.echo(f" Username: {auth_result.username}")
|
175
|
+
click.echo(f" Roles: {', '.join(auth_result.roles)}")
|
176
|
+
click.echo(f" Auth Method: {auth_result.auth_method}")
|
177
|
+
else:
|
178
|
+
click.echo(f"❌ API key authentication failed!", err=True)
|
179
|
+
click.echo(f" Error: {auth_result.error_message}")
|
180
|
+
raise click.Abort()
|
181
|
+
|
182
|
+
except Exception as e:
|
183
|
+
click.echo(f"❌ API key authentication failed: {str(e)}", err=True)
|
184
|
+
raise click.Abort()
|
185
|
+
|
186
|
+
|
187
|
+
@auth.command()
|
188
|
+
@click.option('--token', '-t', required=True,
|
189
|
+
help='JWT token to test')
|
190
|
+
@click.pass_context
|
191
|
+
def test_jwt(ctx, token: str):
|
192
|
+
"""
|
193
|
+
Test JWT token authentication.
|
194
|
+
|
195
|
+
This command tests if a JWT token is valid and returns
|
196
|
+
authentication information.
|
197
|
+
"""
|
198
|
+
try:
|
199
|
+
security_manager = ctx.obj['security_manager']
|
200
|
+
verbose = ctx.obj['verbose']
|
201
|
+
|
202
|
+
if verbose:
|
203
|
+
click.echo(f"Testing JWT token authentication")
|
204
|
+
|
205
|
+
# Test JWT token
|
206
|
+
auth_result = security_manager.auth_manager.authenticate_jwt_token(token)
|
207
|
+
|
208
|
+
if auth_result.is_valid:
|
209
|
+
click.echo(f"✅ JWT token authentication successful!")
|
210
|
+
click.echo(f" Username: {auth_result.username}")
|
211
|
+
click.echo(f" Roles: {', '.join(auth_result.roles)}")
|
212
|
+
click.echo(f" Auth Method: {auth_result.auth_method}")
|
213
|
+
else:
|
214
|
+
click.echo(f"❌ JWT token authentication failed!", err=True)
|
215
|
+
click.echo(f" Error: {auth_result.error_message}")
|
216
|
+
raise click.Abort()
|
217
|
+
|
218
|
+
except Exception as e:
|
219
|
+
click.echo(f"❌ JWT token authentication failed: {str(e)}", err=True)
|
220
|
+
raise click.Abort()
|
221
|
+
|
222
|
+
|
223
|
+
@security_cli.group()
|
224
|
+
@click.pass_context
|
225
|
+
def permissions(ctx):
|
226
|
+
"""
|
227
|
+
Permission management operations.
|
228
|
+
|
229
|
+
Manage permissions including role assignment, permission validation,
|
230
|
+
and permission testing.
|
231
|
+
"""
|
232
|
+
pass
|
233
|
+
|
234
|
+
|
235
|
+
@permissions.command()
|
236
|
+
@click.option('--username', '-u', required=True,
|
237
|
+
help='Username to check permissions for')
|
238
|
+
@click.option('--permissions', '-p', multiple=True, required=True,
|
239
|
+
help='Permissions to check (can be specified multiple times)')
|
240
|
+
@click.pass_context
|
241
|
+
def check(ctx, username: str, permissions: tuple):
|
242
|
+
"""
|
243
|
+
Check user permissions.
|
244
|
+
|
245
|
+
This command checks if a user has the specified permissions.
|
246
|
+
"""
|
247
|
+
try:
|
248
|
+
security_manager = ctx.obj['security_manager']
|
249
|
+
verbose = ctx.obj['verbose']
|
250
|
+
permissions_list = list(permissions)
|
251
|
+
|
252
|
+
if verbose:
|
253
|
+
click.echo(f"Checking permissions for user: {username}")
|
254
|
+
click.echo(f"Required permissions: {', '.join(permissions_list)}")
|
255
|
+
|
256
|
+
# Get user roles
|
257
|
+
user_roles = security_manager.auth_manager._get_user_roles(username)
|
258
|
+
|
259
|
+
if verbose:
|
260
|
+
click.echo(f"User roles: {', '.join(user_roles)}")
|
261
|
+
|
262
|
+
# Check permissions
|
263
|
+
has_permissions = security_manager.permission_manager.validate_access(
|
264
|
+
user_roles, permissions_list
|
265
|
+
)
|
266
|
+
|
267
|
+
if has_permissions.is_valid:
|
268
|
+
click.echo(f"✅ User has all required permissions!")
|
269
|
+
click.echo(f" Username: {username}")
|
270
|
+
click.echo(f" Roles: {', '.join(user_roles)}")
|
271
|
+
click.echo(f" Permissions: {', '.join(permissions_list)}")
|
272
|
+
else:
|
273
|
+
click.echo(f"❌ User does not have required permissions!", err=True)
|
274
|
+
click.echo(f" Username: {username}")
|
275
|
+
click.echo(f" Roles: {', '.join(user_roles)}")
|
276
|
+
click.echo(f" Missing permissions: {', '.join(has_permissions.missing_permissions)}")
|
277
|
+
raise click.Abort()
|
278
|
+
|
279
|
+
except Exception as e:
|
280
|
+
click.echo(f"❌ Permission check failed: {str(e)}", err=True)
|
281
|
+
raise click.Abort()
|
282
|
+
|
283
|
+
|
284
|
+
@permissions.command()
|
285
|
+
@click.option('--role', '-r', required=True,
|
286
|
+
help='Role name')
|
287
|
+
@click.pass_context
|
288
|
+
def list_role_permissions(ctx, role: str):
|
289
|
+
"""
|
290
|
+
List permissions for a role.
|
291
|
+
|
292
|
+
This command displays all permissions assigned to a specific role.
|
293
|
+
"""
|
294
|
+
try:
|
295
|
+
security_manager = ctx.obj['security_manager']
|
296
|
+
verbose = ctx.obj['verbose']
|
297
|
+
|
298
|
+
if verbose:
|
299
|
+
click.echo(f"Listing permissions for role: {role}")
|
300
|
+
|
301
|
+
# Get role permissions
|
302
|
+
permissions = security_manager.permission_manager.get_role_permissions(role)
|
303
|
+
|
304
|
+
if permissions:
|
305
|
+
click.echo(f"Permissions for role '{role}':")
|
306
|
+
for permission in permissions:
|
307
|
+
click.echo(f" - {permission}")
|
308
|
+
else:
|
309
|
+
click.echo(f"No permissions found for role: {role}")
|
310
|
+
|
311
|
+
except Exception as e:
|
312
|
+
click.echo(f"❌ Failed to list role permissions: {str(e)}", err=True)
|
313
|
+
raise click.Abort()
|
314
|
+
|
315
|
+
|
316
|
+
@security_cli.group()
|
317
|
+
@click.pass_context
|
318
|
+
def rate_limit(ctx):
|
319
|
+
"""
|
320
|
+
Rate limiting operations.
|
321
|
+
|
322
|
+
Manage rate limiting including checking limits, resetting limits,
|
323
|
+
and monitoring rate limit status.
|
324
|
+
"""
|
325
|
+
pass
|
326
|
+
|
327
|
+
|
328
|
+
@rate_limit.command()
|
329
|
+
@click.option('--identifier', '-i', required=True,
|
330
|
+
help='Rate limit identifier (IP, user ID, etc.)')
|
331
|
+
@click.pass_context
|
332
|
+
def check(ctx, identifier: str):
|
333
|
+
"""
|
334
|
+
Check rate limit status.
|
335
|
+
|
336
|
+
This command checks the current rate limit status for an identifier.
|
337
|
+
"""
|
338
|
+
try:
|
339
|
+
security_manager = ctx.obj['security_manager']
|
340
|
+
verbose = ctx.obj['verbose']
|
341
|
+
|
342
|
+
if verbose:
|
343
|
+
click.echo(f"Checking rate limit for identifier: {identifier}")
|
344
|
+
|
345
|
+
# Check rate limit
|
346
|
+
is_allowed = security_manager.rate_limiter.check_rate_limit(identifier)
|
347
|
+
|
348
|
+
if is_allowed:
|
349
|
+
click.echo(f"✅ Rate limit check passed for: {identifier}")
|
350
|
+
else:
|
351
|
+
click.echo(f"❌ Rate limit exceeded for: {identifier}", err=True)
|
352
|
+
raise click.Abort()
|
353
|
+
|
354
|
+
except Exception as e:
|
355
|
+
click.echo(f"❌ Rate limit check failed: {str(e)}", err=True)
|
356
|
+
raise click.Abort()
|
357
|
+
|
358
|
+
|
359
|
+
@rate_limit.command()
|
360
|
+
@click.option('--identifier', '-i', required=True,
|
361
|
+
help='Rate limit identifier to reset')
|
362
|
+
@click.pass_context
|
363
|
+
def reset(ctx, identifier: str):
|
364
|
+
"""
|
365
|
+
Reset rate limit for an identifier.
|
366
|
+
|
367
|
+
This command resets the rate limit counter for an identifier.
|
368
|
+
"""
|
369
|
+
try:
|
370
|
+
security_manager = ctx.obj['security_manager']
|
371
|
+
verbose = ctx.obj['verbose']
|
372
|
+
|
373
|
+
if verbose:
|
374
|
+
click.echo(f"Resetting rate limit for identifier: {identifier}")
|
375
|
+
|
376
|
+
# Reset rate limit
|
377
|
+
security_manager.rate_limiter.reset_rate_limit(identifier)
|
378
|
+
|
379
|
+
click.echo(f"✅ Rate limit reset successfully for: {identifier}")
|
380
|
+
|
381
|
+
except Exception as e:
|
382
|
+
click.echo(f"❌ Failed to reset rate limit: {str(e)}", err=True)
|
383
|
+
raise click.Abort()
|
384
|
+
|
385
|
+
|
386
|
+
@rate_limit.command()
|
387
|
+
@click.option('--identifier', '-i', required=True,
|
388
|
+
help='Rate limit identifier to get status for')
|
389
|
+
@click.pass_context
|
390
|
+
def status(ctx, identifier: str):
|
391
|
+
"""
|
392
|
+
Get rate limit status for an identifier.
|
393
|
+
|
394
|
+
This command displays detailed rate limit status information.
|
395
|
+
"""
|
396
|
+
try:
|
397
|
+
security_manager = ctx.obj['security_manager']
|
398
|
+
verbose = ctx.obj['verbose']
|
399
|
+
|
400
|
+
if verbose:
|
401
|
+
click.echo(f"Getting rate limit status for identifier: {identifier}")
|
402
|
+
|
403
|
+
# Get rate limit status
|
404
|
+
status = security_manager.rate_limiter.get_rate_limit_status(identifier)
|
405
|
+
|
406
|
+
click.echo(f"Rate Limit Status for '{identifier}':")
|
407
|
+
click.echo(f" Current Count: {status.current_count}")
|
408
|
+
click.echo(f" Limit: {status.limit}")
|
409
|
+
click.echo(f" Window Start: {status.window_start}")
|
410
|
+
click.echo(f" Window End: {status.window_end}")
|
411
|
+
click.echo(f" Is Allowed: {status.is_allowed}")
|
412
|
+
|
413
|
+
except Exception as e:
|
414
|
+
click.echo(f"❌ Failed to get rate limit status: {str(e)}", err=True)
|
415
|
+
raise click.Abort()
|
416
|
+
|
417
|
+
|
418
|
+
@security_cli.group()
|
419
|
+
@click.pass_context
|
420
|
+
def config(ctx):
|
421
|
+
"""
|
422
|
+
Configuration management operations.
|
423
|
+
|
424
|
+
Manage security configuration including validation, export,
|
425
|
+
and configuration testing.
|
426
|
+
"""
|
427
|
+
pass
|
428
|
+
|
429
|
+
|
430
|
+
@config.command()
|
431
|
+
@click.pass_context
|
432
|
+
def validate(ctx):
|
433
|
+
"""
|
434
|
+
Validate security configuration.
|
435
|
+
|
436
|
+
This command validates the current security configuration
|
437
|
+
and reports any issues.
|
438
|
+
"""
|
439
|
+
try:
|
440
|
+
config = ctx.obj['config']
|
441
|
+
verbose = ctx.obj['verbose']
|
442
|
+
|
443
|
+
if verbose:
|
444
|
+
click.echo("Validating security configuration...")
|
445
|
+
|
446
|
+
# Validate configuration
|
447
|
+
issues = []
|
448
|
+
|
449
|
+
# Check authentication configuration
|
450
|
+
if config.auth and config.auth.enabled:
|
451
|
+
if not config.auth.methods:
|
452
|
+
issues.append("Authentication enabled but no methods specified")
|
453
|
+
|
454
|
+
# Check rate limiting configuration
|
455
|
+
if config.rate_limit and config.rate_limit.enabled:
|
456
|
+
if config.rate_limit.default_requests_per_minute <= 0:
|
457
|
+
issues.append("Invalid rate limit: requests per minute must be positive")
|
458
|
+
|
459
|
+
# Check SSL configuration
|
460
|
+
if config.ssl and config.ssl.enabled:
|
461
|
+
if not config.ssl.cert_file or not config.ssl.key_file:
|
462
|
+
issues.append("SSL enabled but certificate or key file not specified")
|
463
|
+
|
464
|
+
if issues:
|
465
|
+
click.echo(f"❌ Configuration validation failed!", err=True)
|
466
|
+
for issue in issues:
|
467
|
+
click.echo(f" - {issue}")
|
468
|
+
raise click.Abort()
|
469
|
+
else:
|
470
|
+
click.echo(f"✅ Configuration validation passed!")
|
471
|
+
|
472
|
+
except Exception as e:
|
473
|
+
click.echo(f"❌ Configuration validation failed: {str(e)}", err=True)
|
474
|
+
raise click.Abort()
|
475
|
+
|
476
|
+
|
477
|
+
@config.command()
|
478
|
+
@click.option('--output', '-o', type=click.Path(),
|
479
|
+
help='Output file path (default: stdout)')
|
480
|
+
@click.pass_context
|
481
|
+
def export(ctx, output: Optional[str]):
|
482
|
+
"""
|
483
|
+
Export security configuration.
|
484
|
+
|
485
|
+
This command exports the current security configuration
|
486
|
+
to JSON format.
|
487
|
+
"""
|
488
|
+
try:
|
489
|
+
config = ctx.obj['config']
|
490
|
+
verbose = ctx.obj['verbose']
|
491
|
+
|
492
|
+
if verbose:
|
493
|
+
click.echo("Exporting security configuration...")
|
494
|
+
|
495
|
+
# Export configuration
|
496
|
+
config_json = config.model_dump_json(indent=2)
|
497
|
+
|
498
|
+
if output:
|
499
|
+
with open(output, 'w') as f:
|
500
|
+
f.write(config_json)
|
501
|
+
click.echo(f"✅ Configuration exported to: {output}")
|
502
|
+
else:
|
503
|
+
click.echo(config_json)
|
504
|
+
|
505
|
+
except Exception as e:
|
506
|
+
click.echo(f"❌ Failed to export configuration: {str(e)}", err=True)
|
507
|
+
raise click.Abort()
|
508
|
+
|
509
|
+
|
510
|
+
@security_cli.command()
|
511
|
+
@click.option('--output', '-o', type=click.Path(),
|
512
|
+
help='Output file path for roles configuration')
|
513
|
+
@click.option('--template', '-t', is_flag=True,
|
514
|
+
help='Generate template roles configuration')
|
515
|
+
@click.pass_context
|
516
|
+
def generate_roles(ctx, output: Optional[str], template: bool):
|
517
|
+
"""
|
518
|
+
Generate roles configuration file.
|
519
|
+
|
520
|
+
This command generates a roles configuration file with default
|
521
|
+
roles and permissions or a template for customization.
|
522
|
+
"""
|
523
|
+
try:
|
524
|
+
security_manager = ctx.obj['security_manager']
|
525
|
+
verbose = ctx.obj['verbose']
|
526
|
+
|
527
|
+
if verbose:
|
528
|
+
click.echo("Generating roles configuration...")
|
529
|
+
|
530
|
+
if template:
|
531
|
+
# Generate template roles configuration
|
532
|
+
template_roles = {
|
533
|
+
"roles": {
|
534
|
+
"admin": {
|
535
|
+
"description": "Administrator role with full access",
|
536
|
+
"permissions": ["*"],
|
537
|
+
"parent_roles": []
|
538
|
+
},
|
539
|
+
"user": {
|
540
|
+
"description": "Standard user role",
|
541
|
+
"permissions": ["read:own", "write:own"],
|
542
|
+
"parent_roles": []
|
543
|
+
},
|
544
|
+
"guest": {
|
545
|
+
"description": "Guest role with limited access",
|
546
|
+
"permissions": ["read:public"],
|
547
|
+
"parent_roles": []
|
548
|
+
}
|
549
|
+
},
|
550
|
+
"permissions": {
|
551
|
+
"read:own": "Read own resources",
|
552
|
+
"write:own": "Write own resources",
|
553
|
+
"read:public": "Read public resources",
|
554
|
+
"*": "All permissions (wildcard)"
|
555
|
+
}
|
556
|
+
}
|
557
|
+
|
558
|
+
roles_json = json.dumps(template_roles, indent=2)
|
559
|
+
|
560
|
+
if output:
|
561
|
+
with open(output, 'w') as f:
|
562
|
+
f.write(roles_json)
|
563
|
+
click.echo(f"✅ Template roles configuration generated: {output}")
|
564
|
+
else:
|
565
|
+
click.echo(roles_json)
|
566
|
+
else:
|
567
|
+
# Generate current roles configuration
|
568
|
+
roles_config = security_manager.permission_manager.export_roles_config()
|
569
|
+
|
570
|
+
if output:
|
571
|
+
with open(output, 'w') as f:
|
572
|
+
json.dump(roles_config, f, indent=2)
|
573
|
+
click.echo(f"✅ Current roles configuration exported: {output}")
|
574
|
+
else:
|
575
|
+
click.echo(json.dumps(roles_config, indent=2))
|
576
|
+
|
577
|
+
except Exception as e:
|
578
|
+
click.echo(f"❌ Failed to generate roles configuration: {str(e)}", err=True)
|
579
|
+
raise click.Abort()
|
580
|
+
|
581
|
+
|
582
|
+
@security_cli.command()
|
583
|
+
@click.option('--output', '-o', type=click.Path(),
|
584
|
+
help='Output file path for audit report')
|
585
|
+
@click.option('--format', '-f', type=click.Choice(['json', 'text']), default='text',
|
586
|
+
help='Output format for audit report')
|
587
|
+
@click.pass_context
|
588
|
+
def security_audit(ctx, output: Optional[str], format: str):
|
589
|
+
"""
|
590
|
+
Perform security audit.
|
591
|
+
|
592
|
+
This command performs a comprehensive security audit of the
|
593
|
+
system configuration and components.
|
594
|
+
"""
|
595
|
+
try:
|
596
|
+
config = ctx.obj['config']
|
597
|
+
security_manager = ctx.obj['security_manager']
|
598
|
+
verbose = ctx.obj['verbose']
|
599
|
+
|
600
|
+
if verbose:
|
601
|
+
click.echo("Performing security audit...")
|
602
|
+
|
603
|
+
# Perform security audit
|
604
|
+
audit_results = {
|
605
|
+
"timestamp": datetime.now().isoformat(),
|
606
|
+
"configuration": {
|
607
|
+
"authentication": {
|
608
|
+
"enabled": config.auth.enabled if config.auth else False,
|
609
|
+
"methods": config.auth.methods if config.auth else [],
|
610
|
+
"issues": []
|
611
|
+
},
|
612
|
+
"rate_limiting": {
|
613
|
+
"enabled": config.rate_limit.enabled if config.rate_limit else False,
|
614
|
+
"default_limit": config.rate_limit.default_requests_per_minute if config.rate_limit else None,
|
615
|
+
"issues": []
|
616
|
+
},
|
617
|
+
"ssl_tls": {
|
618
|
+
"enabled": config.ssl.enabled if config.ssl else False,
|
619
|
+
"cert_file": config.ssl.cert_file if config.ssl else None,
|
620
|
+
"key_file": config.ssl.key_file if config.ssl else None,
|
621
|
+
"issues": []
|
622
|
+
},
|
623
|
+
"permissions": {
|
624
|
+
"enabled": config.permissions.enabled if config.permissions else False,
|
625
|
+
"roles_file": config.permissions.roles_file if config.permissions else None,
|
626
|
+
"issues": []
|
627
|
+
}
|
628
|
+
},
|
629
|
+
"components": {
|
630
|
+
"auth_manager": {
|
631
|
+
"status": "✅ Initialized" if security_manager.auth_manager else "❌ Not Initialized",
|
632
|
+
"api_keys_count": len(security_manager.auth_manager.api_keys) if security_manager.auth_manager else 0
|
633
|
+
},
|
634
|
+
"permission_manager": {
|
635
|
+
"status": "✅ Initialized" if security_manager.permission_manager else "❌ Not Initialized",
|
636
|
+
"roles_count": len(security_manager.permission_manager.roles) if security_manager.permission_manager else 0
|
637
|
+
},
|
638
|
+
"rate_limiter": {
|
639
|
+
"status": "✅ Initialized" if security_manager.rate_limiter else "❌ Not Initialized"
|
640
|
+
},
|
641
|
+
"ssl_manager": {
|
642
|
+
"status": "✅ Initialized" if security_manager.ssl_manager else "❌ Not Initialized"
|
643
|
+
},
|
644
|
+
"cert_manager": {
|
645
|
+
"status": "✅ Initialized" if security_manager.cert_manager else "❌ Not Initialized"
|
646
|
+
}
|
647
|
+
},
|
648
|
+
"recommendations": []
|
649
|
+
}
|
650
|
+
|
651
|
+
# Add security recommendations
|
652
|
+
if not config.auth.enabled:
|
653
|
+
audit_results["recommendations"].append("Enable authentication for better security")
|
654
|
+
|
655
|
+
if not config.rate_limit.enabled:
|
656
|
+
audit_results["recommendations"].append("Enable rate limiting to prevent abuse")
|
657
|
+
|
658
|
+
if not config.ssl.enabled:
|
659
|
+
audit_results["recommendations"].append("Enable SSL/TLS for secure communication")
|
660
|
+
|
661
|
+
if not config.permissions.enabled:
|
662
|
+
audit_results["recommendations"].append("Enable permissions for access control")
|
663
|
+
|
664
|
+
# Output audit results
|
665
|
+
if format == 'json':
|
666
|
+
audit_json = json.dumps(audit_results, indent=2)
|
667
|
+
if output:
|
668
|
+
with open(output, 'w') as f:
|
669
|
+
f.write(audit_json)
|
670
|
+
click.echo(f"✅ Security audit report saved: {output}")
|
671
|
+
else:
|
672
|
+
click.echo(audit_json)
|
673
|
+
else:
|
674
|
+
# Text format
|
675
|
+
click.echo("Security Audit Report")
|
676
|
+
click.echo("=" * 50)
|
677
|
+
click.echo(f"Timestamp: {audit_results['timestamp']}")
|
678
|
+
click.echo()
|
679
|
+
|
680
|
+
click.echo("Configuration:")
|
681
|
+
click.echo("-" * 20)
|
682
|
+
auth_config = audit_results['configuration']['authentication']
|
683
|
+
click.echo(f"Authentication: {'✅ Enabled' if auth_config['enabled'] else '❌ Disabled'}")
|
684
|
+
if auth_config['methods']:
|
685
|
+
click.echo(f" Methods: {', '.join(auth_config['methods'])}")
|
686
|
+
|
687
|
+
rate_config = audit_results['configuration']['rate_limiting']
|
688
|
+
click.echo(f"Rate Limiting: {'✅ Enabled' if rate_config['enabled'] else '❌ Disabled'}")
|
689
|
+
if rate_config['default_limit']:
|
690
|
+
click.echo(f" Default Limit: {rate_config['default_limit']} requests/minute")
|
691
|
+
|
692
|
+
ssl_config = audit_results['configuration']['ssl_tls']
|
693
|
+
click.echo(f"SSL/TLS: {'✅ Enabled' if ssl_config['enabled'] else '❌ Disabled'}")
|
694
|
+
|
695
|
+
perm_config = audit_results['configuration']['permissions']
|
696
|
+
click.echo(f"Permissions: {'✅ Enabled' if perm_config['enabled'] else '❌ Disabled'}")
|
697
|
+
|
698
|
+
click.echo()
|
699
|
+
click.echo("Components:")
|
700
|
+
click.echo("-" * 20)
|
701
|
+
for name, status in audit_results['components'].items():
|
702
|
+
click.echo(f"{name.replace('_', ' ').title()}: {status['status']}")
|
703
|
+
if 'api_keys_count' in status:
|
704
|
+
click.echo(f" API Keys: {status['api_keys_count']}")
|
705
|
+
if 'roles_count' in status:
|
706
|
+
click.echo(f" Roles: {status['roles_count']}")
|
707
|
+
|
708
|
+
if audit_results['recommendations']:
|
709
|
+
click.echo()
|
710
|
+
click.echo("Recommendations:")
|
711
|
+
click.echo("-" * 20)
|
712
|
+
for rec in audit_results['recommendations']:
|
713
|
+
click.echo(f"• {rec}")
|
714
|
+
|
715
|
+
if output:
|
716
|
+
with open(output, 'w') as f:
|
717
|
+
f.write(json.dumps(audit_results, indent=2))
|
718
|
+
click.echo(f"\n✅ Detailed audit report saved: {output}")
|
719
|
+
|
720
|
+
except Exception as e:
|
721
|
+
click.echo(f"❌ Failed to perform security audit: {str(e)}", err=True)
|
722
|
+
raise click.Abort()
|
723
|
+
|
724
|
+
|
725
|
+
@security_cli.command()
|
726
|
+
@click.pass_context
|
727
|
+
def status(ctx):
|
728
|
+
"""
|
729
|
+
Display security status.
|
730
|
+
|
731
|
+
This command displays the current status of all security
|
732
|
+
components and their configuration.
|
733
|
+
"""
|
734
|
+
try:
|
735
|
+
config = ctx.obj['config']
|
736
|
+
security_manager = ctx.obj['security_manager']
|
737
|
+
verbose = ctx.obj['verbose']
|
738
|
+
|
739
|
+
if verbose:
|
740
|
+
click.echo("Getting security status...")
|
741
|
+
|
742
|
+
click.echo("Security Status:")
|
743
|
+
click.echo("=" * 50)
|
744
|
+
|
745
|
+
# Authentication status
|
746
|
+
auth_enabled = config.auth.enabled if config.auth else False
|
747
|
+
auth_methods = config.auth.methods if config.auth else []
|
748
|
+
click.echo(f"Authentication: {'✅ Enabled' if auth_enabled else '❌ Disabled'}")
|
749
|
+
if auth_methods:
|
750
|
+
click.echo(f" Methods: {', '.join(auth_methods)}")
|
751
|
+
|
752
|
+
# Rate limiting status
|
753
|
+
rate_limit_enabled = config.rate_limit.enabled if config.rate_limit else False
|
754
|
+
click.echo(f"Rate Limiting: {'✅ Enabled' if rate_limit_enabled else '❌ Disabled'}")
|
755
|
+
if rate_limit_enabled and config.rate_limit:
|
756
|
+
click.echo(f" Default Limit: {config.rate_limit.default_requests_per_minute} requests/minute")
|
757
|
+
|
758
|
+
# SSL/TLS status
|
759
|
+
ssl_enabled = config.ssl.enabled if config.ssl else False
|
760
|
+
click.echo(f"SSL/TLS: {'✅ Enabled' if ssl_enabled else '❌ Disabled'}")
|
761
|
+
|
762
|
+
# Permissions status
|
763
|
+
permissions_enabled = config.permissions.enabled if config.permissions else False
|
764
|
+
click.echo(f"Permissions: {'✅ Enabled' if permissions_enabled else '❌ Disabled'}")
|
765
|
+
|
766
|
+
# Component status
|
767
|
+
click.echo("\nComponent Status:")
|
768
|
+
click.echo("-" * 30)
|
769
|
+
|
770
|
+
# Check if components are properly initialized
|
771
|
+
components = [
|
772
|
+
("Auth Manager", security_manager.auth_manager),
|
773
|
+
("Permission Manager", security_manager.permission_manager),
|
774
|
+
("Rate Limiter", security_manager.rate_limiter),
|
775
|
+
("SSL Manager", security_manager.ssl_manager),
|
776
|
+
("Certificate Manager", security_manager.cert_manager),
|
777
|
+
]
|
778
|
+
|
779
|
+
for name, component in components:
|
780
|
+
if component:
|
781
|
+
click.echo(f"{name}: ✅ Initialized")
|
782
|
+
else:
|
783
|
+
click.echo(f"{name}: ❌ Not Initialized")
|
784
|
+
|
785
|
+
except Exception as e:
|
786
|
+
click.echo(f"❌ Failed to get security status: {str(e)}", err=True)
|
787
|
+
raise click.Abort()
|
788
|
+
|
789
|
+
|
790
|
+
if __name__ == '__main__':
|
791
|
+
security_cli()
|