mcp-security-framework 2.0.1__tar.gz → 2.2.0__tar.gz
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-2.0.1 → mcp_security_framework-2.2.0}/PKG-INFO +1 -1
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/__init__.py +1 -1
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/cli/cert_cli.py +69 -52
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/cli/security_cli.py +31 -23
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/core/auth_manager.py +10 -17
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/core/cert_manager.py +139 -23
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/core/rate_limiter.py +3 -3
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/core/security_manager.py +16 -6
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/core/ssl_manager.py +305 -73
- mcp_security_framework-2.2.0/mcp_security_framework/examples/cert_inspection_example.py +391 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/examples/comprehensive_example.py +71 -38
- mcp_security_framework-2.2.0/mcp_security_framework/examples/crl_retrieval_example.py +385 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/examples/fastapi_example.py +260 -18
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/examples/flask_example.py +20 -3
- mcp_security_framework-2.2.0/mcp_security_framework/examples/protected_keys_example.py +297 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/examples/standalone_example.py +42 -22
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/middleware/auth_middleware.py +13 -3
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/middleware/fastapi_auth_middleware.py +4 -27
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/middleware/flask_auth_middleware.py +4 -27
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/schemas/config.py +124 -28
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/schemas/models.py +31 -16
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/schemas/responses.py +54 -13
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/utils/__init__.py +27 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/utils/cert_utils.py +286 -56
- mcp_security_framework-2.2.0/mcp_security_framework/utils/crl_client.py +408 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/utils/crypto_utils.py +65 -21
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/utils/datetime_compat.py +15 -10
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/utils/validation_utils.py +34 -8
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework.egg-info/PKG-INFO +1 -1
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework.egg-info/SOURCES.txt +4 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/pyproject.toml +1 -1
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/LICENSE +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/README.md +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/cli/__init__.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/constants.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/core/__init__.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/core/adapter_wrapper.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/core/audit_logger.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/core/permission_manager.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/core/security_adapter.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/examples/__init__.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/examples/adapter_example.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/examples/gateway_example.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/examples/microservice_example.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/middleware/__init__.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/middleware/fastapi_middleware.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/middleware/flask_middleware.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/middleware/mtls_middleware.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/middleware/rate_limit_middleware.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/middleware/security_middleware.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/schemas/__init__.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/schemas/operation_context.py +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework.egg-info/dependency_links.txt +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework.egg-info/entry_points.txt +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework.egg-info/requires.txt +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework.egg-info/top_level.txt +0 -0
- {mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-security-framework
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Universal security framework for microservices with SSL/TLS, authentication, authorization, and rate limiting. Added extensible security adapter system for custom operation validation, structured audit logging, and operation context support. Comprehensive test coverage with 1104+ tests. Fully backward compatible. Requires cryptography>=42.0.0 for certificate operations.
|
|
5
5
|
Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
|
6
6
|
Maintainer-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
{mcp_security_framework-2.0.1 → mcp_security_framework-2.2.0}/mcp_security_framework/cli/cert_cli.py
RENAMED
|
@@ -27,7 +27,7 @@ License: MIT
|
|
|
27
27
|
|
|
28
28
|
import json
|
|
29
29
|
import os
|
|
30
|
-
from typing import Optional
|
|
30
|
+
from typing import List, Optional, Tuple
|
|
31
31
|
|
|
32
32
|
import click
|
|
33
33
|
|
|
@@ -47,7 +47,7 @@ from ..schemas.config import (
|
|
|
47
47
|
)
|
|
48
48
|
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
|
|
49
49
|
@click.pass_context
|
|
50
|
-
def cert_cli(ctx, config_path: Optional[str], verbose: bool):
|
|
50
|
+
def cert_cli(ctx: click.Context, config_path: Optional[str], verbose: bool) -> None:
|
|
51
51
|
"""
|
|
52
52
|
Certificate Management CLI
|
|
53
53
|
|
|
@@ -88,7 +88,7 @@ def cert_cli(ctx, config_path: Optional[str], verbose: bool):
|
|
|
88
88
|
# _get_cert_manager(ctx), only once its own body actually runs.
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
def _get_cert_manager(ctx) -> CertificateManager:
|
|
91
|
+
def _get_cert_manager(ctx: click.Context) -> CertificateManager:
|
|
92
92
|
"""
|
|
93
93
|
Lazily construct (and cache) the CertificateManager for this invocation.
|
|
94
94
|
|
|
@@ -110,10 +110,11 @@ def _get_cert_manager(ctx) -> CertificateManager:
|
|
|
110
110
|
"""
|
|
111
111
|
if "cert_manager" not in ctx.obj:
|
|
112
112
|
ctx.obj["cert_manager"] = CertificateManager(ctx.obj["config"])
|
|
113
|
-
|
|
113
|
+
cert_manager: CertificateManager = ctx.obj["cert_manager"]
|
|
114
|
+
return cert_manager
|
|
114
115
|
|
|
115
116
|
|
|
116
|
-
def _validate_permissions(permissions:
|
|
117
|
+
def _validate_permissions(permissions: Tuple[str, ...]) -> List[str]:
|
|
117
118
|
"""
|
|
118
119
|
Validate permission strings supplied via ``--permissions``.
|
|
119
120
|
|
|
@@ -179,7 +180,7 @@ def _validate_permissions(permissions: tuple) -> list:
|
|
|
179
180
|
@click.option("--key-size", "-k", default=2048, help="RSA key size")
|
|
180
181
|
@click.pass_context
|
|
181
182
|
def create_ca(
|
|
182
|
-
ctx,
|
|
183
|
+
ctx: click.Context,
|
|
183
184
|
common_name: str,
|
|
184
185
|
organization: str,
|
|
185
186
|
country: str,
|
|
@@ -188,7 +189,7 @@ def create_ca(
|
|
|
188
189
|
email: Optional[str],
|
|
189
190
|
validity_years: int,
|
|
190
191
|
key_size: int,
|
|
191
|
-
):
|
|
192
|
+
) -> None:
|
|
192
193
|
"""
|
|
193
194
|
Create a root CA certificate.
|
|
194
195
|
|
|
@@ -263,7 +264,7 @@ def create_ca(
|
|
|
263
264
|
@click.option("--ca-key", required=True, help="Path to the signing CA private key")
|
|
264
265
|
@click.pass_context
|
|
265
266
|
def create_server(
|
|
266
|
-
ctx,
|
|
267
|
+
ctx: click.Context,
|
|
267
268
|
common_name: str,
|
|
268
269
|
organization: str,
|
|
269
270
|
country: str,
|
|
@@ -272,12 +273,12 @@ def create_server(
|
|
|
272
273
|
email: Optional[str],
|
|
273
274
|
validity_days: int,
|
|
274
275
|
key_size: int,
|
|
275
|
-
san:
|
|
276
|
-
key_usage:
|
|
277
|
-
extended_key_usage:
|
|
276
|
+
san: Tuple[str, ...],
|
|
277
|
+
key_usage: Tuple[str, ...],
|
|
278
|
+
extended_key_usage: Tuple[str, ...],
|
|
278
279
|
ca_cert: str,
|
|
279
280
|
ca_key: str,
|
|
280
|
-
):
|
|
281
|
+
) -> None:
|
|
281
282
|
"""
|
|
282
283
|
Create a server certificate.
|
|
283
284
|
|
|
@@ -287,30 +288,40 @@ def create_server(
|
|
|
287
288
|
cert_manager = _get_cert_manager(ctx)
|
|
288
289
|
verbose = ctx.obj["verbose"]
|
|
289
290
|
|
|
290
|
-
# Create server configuration
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
291
|
+
# Create server configuration.
|
|
292
|
+
#
|
|
293
|
+
# key_usage/extended_key_usage fall back to ServerCertConfig's own
|
|
294
|
+
# field defaults (read from the model rather than duplicated here)
|
|
295
|
+
# when the flag is not given, instead of an empty list -- an empty
|
|
296
|
+
# list would silently strip the default key usage extensions from
|
|
297
|
+
# every certificate that does not pass --key-usage explicitly.
|
|
298
|
+
# Reading the defaults from the model, rather than a **kwargs dict
|
|
299
|
+
# assembled conditionally, also keeps every field's real type
|
|
300
|
+
# (str, Optional[str], int, List[str]) visible to the type checker
|
|
301
|
+
# instead of collapsing them into one imprecise union.
|
|
302
|
+
key_usage_default = ServerCertConfig.model_fields["key_usage"].default
|
|
303
|
+
extended_key_usage_default = ServerCertConfig.model_fields[
|
|
304
|
+
"extended_key_usage"
|
|
305
|
+
].default
|
|
306
|
+
server_config = ServerCertConfig(
|
|
307
|
+
common_name=common_name,
|
|
308
|
+
organization=organization,
|
|
309
|
+
country=country,
|
|
310
|
+
state=state,
|
|
311
|
+
locality=locality,
|
|
312
|
+
email=email,
|
|
313
|
+
validity_days=validity_days,
|
|
314
|
+
key_size=key_size,
|
|
315
|
+
subject_alt_names=list(san) if san else [],
|
|
316
|
+
key_usage=list(key_usage) if key_usage else list(key_usage_default),
|
|
317
|
+
extended_key_usage=(
|
|
318
|
+
list(extended_key_usage)
|
|
319
|
+
if extended_key_usage
|
|
320
|
+
else list(extended_key_usage_default)
|
|
321
|
+
),
|
|
322
|
+
ca_cert_path=ca_cert,
|
|
323
|
+
ca_key_path=ca_key,
|
|
324
|
+
)
|
|
314
325
|
|
|
315
326
|
if verbose:
|
|
316
327
|
click.echo("Creating server certificate with configuration:")
|
|
@@ -365,7 +376,7 @@ def create_server(
|
|
|
365
376
|
@click.option("--ca-key", required=True, help="Path to the signing CA private key")
|
|
366
377
|
@click.pass_context
|
|
367
378
|
def create_client(
|
|
368
|
-
ctx,
|
|
379
|
+
ctx: click.Context,
|
|
369
380
|
common_name: str,
|
|
370
381
|
organization: str,
|
|
371
382
|
country: str,
|
|
@@ -374,11 +385,11 @@ def create_client(
|
|
|
374
385
|
email: Optional[str],
|
|
375
386
|
validity_days: int,
|
|
376
387
|
key_size: int,
|
|
377
|
-
roles:
|
|
378
|
-
permissions:
|
|
388
|
+
roles: Tuple[str, ...],
|
|
389
|
+
permissions: Tuple[str, ...],
|
|
379
390
|
ca_cert: str,
|
|
380
391
|
ca_key: str,
|
|
381
|
-
):
|
|
392
|
+
) -> None:
|
|
382
393
|
"""
|
|
383
394
|
Create a client certificate.
|
|
384
395
|
|
|
@@ -443,7 +454,9 @@ def create_client(
|
|
|
443
454
|
help="Path to CRL file for revocation check",
|
|
444
455
|
)
|
|
445
456
|
@click.pass_context
|
|
446
|
-
def validate(
|
|
457
|
+
def validate(
|
|
458
|
+
ctx: click.Context, cert_path: str, ca_cert: Optional[str], crl: Optional[str]
|
|
459
|
+
) -> None:
|
|
447
460
|
"""
|
|
448
461
|
Validate a certificate.
|
|
449
462
|
|
|
@@ -480,7 +493,7 @@ def validate(ctx, cert_path: str, ca_cert: Optional[str], crl: Optional[str]):
|
|
|
480
493
|
@cert_cli.command()
|
|
481
494
|
@click.argument("cert_path", type=click.Path(exists=True))
|
|
482
495
|
@click.pass_context
|
|
483
|
-
def info(ctx, cert_path: str):
|
|
496
|
+
def info(ctx: click.Context, cert_path: str) -> None:
|
|
484
497
|
"""
|
|
485
498
|
Display certificate information.
|
|
486
499
|
|
|
@@ -543,7 +556,7 @@ def info(ctx, cert_path: str):
|
|
|
543
556
|
)
|
|
544
557
|
@click.pass_context
|
|
545
558
|
def create_intermediate_ca(
|
|
546
|
-
ctx,
|
|
559
|
+
ctx: click.Context,
|
|
547
560
|
common_name: str,
|
|
548
561
|
organization: str,
|
|
549
562
|
country: str,
|
|
@@ -554,7 +567,7 @@ def create_intermediate_ca(
|
|
|
554
567
|
key_size: int,
|
|
555
568
|
parent_ca_cert: str,
|
|
556
569
|
parent_ca_key: str,
|
|
557
|
-
):
|
|
570
|
+
) -> None:
|
|
558
571
|
"""
|
|
559
572
|
Create an intermediate CA certificate.
|
|
560
573
|
|
|
@@ -612,8 +625,12 @@ def create_intermediate_ca(
|
|
|
612
625
|
@click.option("--validity-days", "-d", default=30, help="CRL validity in days")
|
|
613
626
|
@click.pass_context
|
|
614
627
|
def create_crl(
|
|
615
|
-
ctx
|
|
616
|
-
|
|
628
|
+
ctx: click.Context,
|
|
629
|
+
ca_cert: str,
|
|
630
|
+
ca_key: str,
|
|
631
|
+
output: Optional[str],
|
|
632
|
+
validity_days: int,
|
|
633
|
+
) -> None:
|
|
617
634
|
"""
|
|
618
635
|
Create a Certificate Revocation List (CRL).
|
|
619
636
|
|
|
@@ -653,7 +670,7 @@ def create_crl(
|
|
|
653
670
|
@click.argument("serial_number")
|
|
654
671
|
@click.option("--reason", "-r", default="unspecified", help="Reason for revocation")
|
|
655
672
|
@click.pass_context
|
|
656
|
-
def revoke(ctx, serial_number: str, reason: str):
|
|
673
|
+
def revoke(ctx: click.Context, serial_number: str, reason: str) -> None:
|
|
657
674
|
"""
|
|
658
675
|
Revoke a certificate.
|
|
659
676
|
|
|
@@ -690,7 +707,7 @@ def revoke(ctx, serial_number: str, reason: str):
|
|
|
690
707
|
help="Path to CRL file for revocation check",
|
|
691
708
|
)
|
|
692
709
|
@click.pass_context
|
|
693
|
-
def check_revocation(ctx, cert_path: str, crl: Optional[str]):
|
|
710
|
+
def check_revocation(ctx: click.Context, cert_path: str, crl: Optional[str]) -> None:
|
|
694
711
|
"""
|
|
695
712
|
Check if certificate is revoked according to CRL.
|
|
696
713
|
|
|
@@ -726,7 +743,7 @@ def check_revocation(ctx, cert_path: str, crl: Optional[str]):
|
|
|
726
743
|
help="Path to CRL file for detailed revocation check",
|
|
727
744
|
)
|
|
728
745
|
@click.pass_context
|
|
729
|
-
def revocation_info(ctx, cert_path: str, crl: Optional[str]):
|
|
746
|
+
def revocation_info(ctx: click.Context, cert_path: str, crl: Optional[str]) -> None:
|
|
730
747
|
"""
|
|
731
748
|
Get detailed revocation information for certificate.
|
|
732
749
|
|
|
@@ -742,7 +759,7 @@ def revocation_info(ctx, cert_path: str, crl: Optional[str]):
|
|
|
742
759
|
if crl:
|
|
743
760
|
click.echo(f"Using CRL file: {crl}")
|
|
744
761
|
|
|
745
|
-
# Get detailed revocation information
|
|
762
|
+
# Get detailed revocation information.
|
|
746
763
|
revocation_info = cert_manager.validate_certificate_against_crl(cert_path, crl)
|
|
747
764
|
|
|
748
765
|
click.echo(f"Certificate Serial Number: {revocation_info['serial_number']}")
|
|
@@ -765,7 +782,7 @@ def revocation_info(ctx, cert_path: str, crl: Optional[str]):
|
|
|
765
782
|
@cert_cli.command()
|
|
766
783
|
@click.argument("crl_path", type=click.Path(exists=True))
|
|
767
784
|
@click.pass_context
|
|
768
|
-
def crl_info(ctx, crl_path: str):
|
|
785
|
+
def crl_info(ctx: click.Context, crl_path: str) -> None:
|
|
769
786
|
"""
|
|
770
787
|
Display CRL information.
|
|
771
788
|
|
|
@@ -807,7 +824,7 @@ def crl_info(ctx, crl_path: str):
|
|
|
807
824
|
@cert_cli.command()
|
|
808
825
|
@click.argument("crl_path", type=click.Path(exists=True))
|
|
809
826
|
@click.pass_context
|
|
810
|
-
def validate_crl(ctx, crl_path: str):
|
|
827
|
+
def validate_crl(ctx: click.Context, crl_path: str) -> None:
|
|
811
828
|
"""
|
|
812
829
|
Validate CRL file.
|
|
813
830
|
|
|
@@ -27,7 +27,7 @@ License: MIT
|
|
|
27
27
|
import json
|
|
28
28
|
import os
|
|
29
29
|
from datetime import datetime
|
|
30
|
-
from typing import Optional
|
|
30
|
+
from typing import Any, Dict, Optional, Tuple
|
|
31
31
|
|
|
32
32
|
import click
|
|
33
33
|
|
|
@@ -47,7 +47,7 @@ from ..schemas.config import (
|
|
|
47
47
|
)
|
|
48
48
|
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
|
|
49
49
|
@click.pass_context
|
|
50
|
-
def security_cli(ctx, config_path: Optional[str], verbose: bool):
|
|
50
|
+
def security_cli(ctx: click.Context, config_path: Optional[str], verbose: bool) -> None:
|
|
51
51
|
"""
|
|
52
52
|
Security Management CLI
|
|
53
53
|
|
|
@@ -90,7 +90,7 @@ def security_cli(ctx, config_path: Optional[str], verbose: bool):
|
|
|
90
90
|
# _get_security_manager(ctx), only once its own body actually runs.
|
|
91
91
|
|
|
92
92
|
|
|
93
|
-
def _get_security_manager(ctx) -> SecurityManager:
|
|
93
|
+
def _get_security_manager(ctx: click.Context) -> SecurityManager:
|
|
94
94
|
"""
|
|
95
95
|
Lazily construct (and cache) the SecurityManager for this invocation.
|
|
96
96
|
|
|
@@ -113,12 +113,13 @@ def _get_security_manager(ctx) -> SecurityManager:
|
|
|
113
113
|
"""
|
|
114
114
|
if "security_manager" not in ctx.obj:
|
|
115
115
|
ctx.obj["security_manager"] = SecurityManager(ctx.obj["config"])
|
|
116
|
-
|
|
116
|
+
security_manager: SecurityManager = ctx.obj["security_manager"]
|
|
117
|
+
return security_manager
|
|
117
118
|
|
|
118
119
|
|
|
119
120
|
@security_cli.group()
|
|
120
121
|
@click.pass_context
|
|
121
|
-
def auth(ctx):
|
|
122
|
+
def auth(ctx: click.Context) -> None:
|
|
122
123
|
"""
|
|
123
124
|
Authentication operations.
|
|
124
125
|
|
|
@@ -132,7 +133,7 @@ def auth(ctx):
|
|
|
132
133
|
@click.option("--username", "-u", required=True, help="Username for the API key")
|
|
133
134
|
@click.option("--api-key", "-k", required=True, help="API key value")
|
|
134
135
|
@click.pass_context
|
|
135
|
-
def add_api_key(ctx, username: str, api_key: str):
|
|
136
|
+
def add_api_key(ctx: click.Context, username: str, api_key: str) -> None:
|
|
136
137
|
"""
|
|
137
138
|
Add an API key for a user.
|
|
138
139
|
|
|
@@ -163,7 +164,7 @@ def add_api_key(ctx, username: str, api_key: str):
|
|
|
163
164
|
@auth.command()
|
|
164
165
|
@click.option("--username", "-u", required=True, help="Username to remove API key for")
|
|
165
166
|
@click.pass_context
|
|
166
|
-
def remove_api_key(ctx, username: str):
|
|
167
|
+
def remove_api_key(ctx: click.Context, username: str) -> None:
|
|
167
168
|
"""
|
|
168
169
|
Remove an API key for a user.
|
|
169
170
|
|
|
@@ -194,7 +195,7 @@ def remove_api_key(ctx, username: str):
|
|
|
194
195
|
@auth.command()
|
|
195
196
|
@click.option("--api-key", "-k", required=True, help="API key to test")
|
|
196
197
|
@click.pass_context
|
|
197
|
-
def test_api_key(ctx, api_key: str):
|
|
198
|
+
def test_api_key(ctx: click.Context, api_key: str) -> None:
|
|
198
199
|
"""
|
|
199
200
|
Test API key authentication.
|
|
200
201
|
|
|
@@ -229,7 +230,7 @@ def test_api_key(ctx, api_key: str):
|
|
|
229
230
|
@auth.command()
|
|
230
231
|
@click.option("--token", "-t", required=True, help="JWT token to test")
|
|
231
232
|
@click.pass_context
|
|
232
|
-
def test_jwt(ctx, token: str):
|
|
233
|
+
def test_jwt(ctx: click.Context, token: str) -> None:
|
|
233
234
|
"""
|
|
234
235
|
Test JWT token authentication.
|
|
235
236
|
|
|
@@ -263,7 +264,7 @@ def test_jwt(ctx, token: str):
|
|
|
263
264
|
|
|
264
265
|
@security_cli.group()
|
|
265
266
|
@click.pass_context
|
|
266
|
-
def permissions(ctx):
|
|
267
|
+
def permissions(ctx: click.Context) -> None:
|
|
267
268
|
"""
|
|
268
269
|
Permission management operations.
|
|
269
270
|
|
|
@@ -285,7 +286,7 @@ def permissions(ctx):
|
|
|
285
286
|
help="Permissions to check (can be specified multiple times)",
|
|
286
287
|
)
|
|
287
288
|
@click.pass_context
|
|
288
|
-
def check(ctx, username: str, permissions:
|
|
289
|
+
def check(ctx: click.Context, username: str, permissions: Tuple[str, ...]) -> None:
|
|
289
290
|
"""
|
|
290
291
|
Check user permissions.
|
|
291
292
|
|
|
@@ -334,7 +335,7 @@ def check(ctx, username: str, permissions: tuple):
|
|
|
334
335
|
@permissions.command()
|
|
335
336
|
@click.option("--role", "-r", required=True, help="Role name")
|
|
336
337
|
@click.pass_context
|
|
337
|
-
def list_role_permissions(ctx, role: str):
|
|
338
|
+
def list_role_permissions(ctx: click.Context, role: str) -> None:
|
|
338
339
|
"""
|
|
339
340
|
List permissions for a role.
|
|
340
341
|
|
|
@@ -364,7 +365,7 @@ def list_role_permissions(ctx, role: str):
|
|
|
364
365
|
|
|
365
366
|
@security_cli.group()
|
|
366
367
|
@click.pass_context
|
|
367
|
-
def rate_limit(ctx):
|
|
368
|
+
def rate_limit(ctx: click.Context) -> None:
|
|
368
369
|
"""
|
|
369
370
|
Rate limiting operations.
|
|
370
371
|
|
|
@@ -382,7 +383,7 @@ def rate_limit(ctx):
|
|
|
382
383
|
help="Rate limit identifier (IP, user ID, etc.)",
|
|
383
384
|
)
|
|
384
385
|
@click.pass_context
|
|
385
|
-
def rate_limit_check(ctx, identifier: str):
|
|
386
|
+
def rate_limit_check(ctx: click.Context, identifier: str) -> None:
|
|
386
387
|
"""
|
|
387
388
|
Check rate limit status.
|
|
388
389
|
|
|
@@ -414,7 +415,7 @@ def rate_limit_check(ctx, identifier: str):
|
|
|
414
415
|
"--identifier", "-i", required=True, help="Rate limit identifier to reset"
|
|
415
416
|
)
|
|
416
417
|
@click.pass_context
|
|
417
|
-
def reset(ctx, identifier: str):
|
|
418
|
+
def reset(ctx: click.Context, identifier: str) -> None:
|
|
418
419
|
"""
|
|
419
420
|
Reset rate limit for an identifier.
|
|
420
421
|
|
|
@@ -442,7 +443,7 @@ def reset(ctx, identifier: str):
|
|
|
442
443
|
"--identifier", "-i", required=True, help="Rate limit identifier to get status for"
|
|
443
444
|
)
|
|
444
445
|
@click.pass_context
|
|
445
|
-
def rate_limit_status(ctx, identifier: str):
|
|
446
|
+
def rate_limit_status(ctx: click.Context, identifier: str) -> None:
|
|
446
447
|
"""
|
|
447
448
|
Get rate limit status for an identifier.
|
|
448
449
|
|
|
@@ -472,7 +473,7 @@ def rate_limit_status(ctx, identifier: str):
|
|
|
472
473
|
|
|
473
474
|
@security_cli.group()
|
|
474
475
|
@click.pass_context
|
|
475
|
-
def config(ctx):
|
|
476
|
+
def config(ctx: click.Context) -> None:
|
|
476
477
|
"""
|
|
477
478
|
Configuration management operations.
|
|
478
479
|
|
|
@@ -484,7 +485,7 @@ def config(ctx):
|
|
|
484
485
|
|
|
485
486
|
@config.command()
|
|
486
487
|
@click.pass_context
|
|
487
|
-
def validate(ctx):
|
|
488
|
+
def validate(ctx: click.Context) -> None:
|
|
488
489
|
"""
|
|
489
490
|
Validate security configuration.
|
|
490
491
|
|
|
@@ -536,7 +537,7 @@ def validate(ctx):
|
|
|
536
537
|
"--output", "-o", type=click.Path(), help="Output file path (default: stdout)"
|
|
537
538
|
)
|
|
538
539
|
@click.pass_context
|
|
539
|
-
def export(ctx, output: Optional[str]):
|
|
540
|
+
def export(ctx: click.Context, output: Optional[str]) -> None:
|
|
540
541
|
"""
|
|
541
542
|
Export security configuration.
|
|
542
543
|
|
|
@@ -573,7 +574,7 @@ def export(ctx, output: Optional[str]):
|
|
|
573
574
|
"--template", "-t", is_flag=True, help="Generate template roles configuration"
|
|
574
575
|
)
|
|
575
576
|
@click.pass_context
|
|
576
|
-
def generate_roles(ctx, output: Optional[str], template: bool):
|
|
577
|
+
def generate_roles(ctx: click.Context, output: Optional[str], template: bool) -> None:
|
|
577
578
|
"""
|
|
578
579
|
Generate roles configuration file.
|
|
579
580
|
|
|
@@ -651,7 +652,7 @@ def generate_roles(ctx, output: Optional[str], template: bool):
|
|
|
651
652
|
help="Output format for audit report",
|
|
652
653
|
)
|
|
653
654
|
@click.pass_context
|
|
654
|
-
def security_audit(ctx, output: Optional[str], format: str):
|
|
655
|
+
def security_audit(ctx: click.Context, output: Optional[str], format: str) -> None:
|
|
655
656
|
"""
|
|
656
657
|
Perform security audit.
|
|
657
658
|
|
|
@@ -667,7 +668,14 @@ def security_audit(ctx, output: Optional[str], format: str):
|
|
|
667
668
|
click.echo("Performing security audit...")
|
|
668
669
|
|
|
669
670
|
# Perform security audit
|
|
670
|
-
|
|
671
|
+
#
|
|
672
|
+
# This report is a genuinely heterogeneous JSON-shaped structure:
|
|
673
|
+
# values are bools, strings, lists, dicts, and Nones. It is built once
|
|
674
|
+
# and either serialised (json.dumps) or rendered as text below, so a
|
|
675
|
+
# precise per-key TypedDict would buy nothing here. Dict[str, Any] is
|
|
676
|
+
# the honest, minimal-loss type for exactly this shape -- not a
|
|
677
|
+
# stand-in for a type that could otherwise be pinned down.
|
|
678
|
+
audit_results: Dict[str, Any] = {
|
|
671
679
|
"timestamp": datetime.now().isoformat(),
|
|
672
680
|
"configuration": {
|
|
673
681
|
"authentication": {
|
|
@@ -849,7 +857,7 @@ def security_audit(ctx, output: Optional[str], format: str):
|
|
|
849
857
|
|
|
850
858
|
@security_cli.command()
|
|
851
859
|
@click.pass_context
|
|
852
|
-
def status(ctx):
|
|
860
|
+
def status(ctx: click.Context) -> None:
|
|
853
861
|
"""
|
|
854
862
|
Display security status.
|
|
855
863
|
|
|
@@ -46,6 +46,7 @@ from ..utils.crypto_utils import (
|
|
|
46
46
|
from ..utils.datetime_compat import (
|
|
47
47
|
get_not_valid_after_utc,
|
|
48
48
|
)
|
|
49
|
+
from .cert_manager import CertificateValidationError
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
class AuthManager:
|
|
@@ -697,7 +698,8 @@ class AuthManager:
|
|
|
697
698
|
},
|
|
698
699
|
)
|
|
699
700
|
raise CertificateValidationError(
|
|
700
|
-
f"Certificate authentication failed: {str(e)}"
|
|
701
|
+
f"Certificate authentication failed: {str(e)}",
|
|
702
|
+
error_code=-32004,
|
|
701
703
|
)
|
|
702
704
|
|
|
703
705
|
def create_jwt_token(self, user_data: Dict) -> str:
|
|
@@ -1087,19 +1089,10 @@ class JWTValidationError(Exception):
|
|
|
1087
1089
|
super().__init__(self.message)
|
|
1088
1090
|
|
|
1089
1091
|
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
Args:
|
|
1098
|
-
message (str): Human-readable error message describing
|
|
1099
|
-
the certificate validation failure.
|
|
1100
|
-
error_code (int): Error code for the certificate validation
|
|
1101
|
-
error. Defaults to -32004.
|
|
1102
|
-
"""
|
|
1103
|
-
self.message = message
|
|
1104
|
-
self.error_code = error_code
|
|
1105
|
-
super().__init__(self.message)
|
|
1092
|
+
# ``CertificateValidationError`` is defined once, in ``cert_manager``, and
|
|
1093
|
+
# re-exported here so that ``from .auth_manager import CertificateValidationError``
|
|
1094
|
+
# keeps working. Every module in this package that raises a certificate
|
|
1095
|
+
# validation failure must raise (and every caller must be able to catch) the
|
|
1096
|
+
# SAME class -- see ``cert_manager.CertificateValidationError`` for the
|
|
1097
|
+
# rationale and for why the per-site error code is passed at construction
|
|
1098
|
+
# instead of being encoded as a second, look-alike class.
|