truthound 1.0.8__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.
- truthound/__init__.py +162 -0
- truthound/adapters.py +100 -0
- truthound/api.py +365 -0
- truthound/audit/__init__.py +248 -0
- truthound/audit/core.py +967 -0
- truthound/audit/filters.py +620 -0
- truthound/audit/formatters.py +707 -0
- truthound/audit/logger.py +902 -0
- truthound/audit/middleware.py +571 -0
- truthound/audit/storage.py +1083 -0
- truthound/benchmark/__init__.py +123 -0
- truthound/benchmark/base.py +757 -0
- truthound/benchmark/comparison.py +635 -0
- truthound/benchmark/generators.py +706 -0
- truthound/benchmark/reporters.py +718 -0
- truthound/benchmark/runner.py +635 -0
- truthound/benchmark/scenarios.py +712 -0
- truthound/cache.py +252 -0
- truthound/checkpoint/__init__.py +136 -0
- truthound/checkpoint/actions/__init__.py +164 -0
- truthound/checkpoint/actions/base.py +324 -0
- truthound/checkpoint/actions/custom.py +234 -0
- truthound/checkpoint/actions/discord_notify.py +290 -0
- truthound/checkpoint/actions/email_notify.py +405 -0
- truthound/checkpoint/actions/github_action.py +406 -0
- truthound/checkpoint/actions/opsgenie.py +1499 -0
- truthound/checkpoint/actions/pagerduty.py +226 -0
- truthound/checkpoint/actions/slack_notify.py +233 -0
- truthound/checkpoint/actions/store_result.py +249 -0
- truthound/checkpoint/actions/teams_notify.py +1570 -0
- truthound/checkpoint/actions/telegram_notify.py +419 -0
- truthound/checkpoint/actions/update_docs.py +552 -0
- truthound/checkpoint/actions/webhook.py +293 -0
- truthound/checkpoint/analytics/__init__.py +147 -0
- truthound/checkpoint/analytics/aggregations/__init__.py +23 -0
- truthound/checkpoint/analytics/aggregations/rollup.py +481 -0
- truthound/checkpoint/analytics/aggregations/time_bucket.py +306 -0
- truthound/checkpoint/analytics/analyzers/__init__.py +17 -0
- truthound/checkpoint/analytics/analyzers/anomaly.py +386 -0
- truthound/checkpoint/analytics/analyzers/base.py +270 -0
- truthound/checkpoint/analytics/analyzers/forecast.py +421 -0
- truthound/checkpoint/analytics/analyzers/trend.py +314 -0
- truthound/checkpoint/analytics/models.py +292 -0
- truthound/checkpoint/analytics/protocols.py +549 -0
- truthound/checkpoint/analytics/service.py +718 -0
- truthound/checkpoint/analytics/stores/__init__.py +16 -0
- truthound/checkpoint/analytics/stores/base.py +306 -0
- truthound/checkpoint/analytics/stores/memory_store.py +353 -0
- truthound/checkpoint/analytics/stores/sqlite_store.py +557 -0
- truthound/checkpoint/analytics/stores/timescale_store.py +501 -0
- truthound/checkpoint/async_actions.py +794 -0
- truthound/checkpoint/async_base.py +708 -0
- truthound/checkpoint/async_checkpoint.py +617 -0
- truthound/checkpoint/async_runner.py +639 -0
- truthound/checkpoint/checkpoint.py +527 -0
- truthound/checkpoint/ci/__init__.py +61 -0
- truthound/checkpoint/ci/detector.py +355 -0
- truthound/checkpoint/ci/reporter.py +436 -0
- truthound/checkpoint/ci/templates.py +454 -0
- truthound/checkpoint/circuitbreaker/__init__.py +133 -0
- truthound/checkpoint/circuitbreaker/breaker.py +542 -0
- truthound/checkpoint/circuitbreaker/core.py +252 -0
- truthound/checkpoint/circuitbreaker/detection.py +459 -0
- truthound/checkpoint/circuitbreaker/middleware.py +389 -0
- truthound/checkpoint/circuitbreaker/registry.py +357 -0
- truthound/checkpoint/distributed/__init__.py +139 -0
- truthound/checkpoint/distributed/backends/__init__.py +35 -0
- truthound/checkpoint/distributed/backends/celery_backend.py +503 -0
- truthound/checkpoint/distributed/backends/kubernetes_backend.py +696 -0
- truthound/checkpoint/distributed/backends/local_backend.py +397 -0
- truthound/checkpoint/distributed/backends/ray_backend.py +625 -0
- truthound/checkpoint/distributed/base.py +774 -0
- truthound/checkpoint/distributed/orchestrator.py +765 -0
- truthound/checkpoint/distributed/protocols.py +842 -0
- truthound/checkpoint/distributed/registry.py +449 -0
- truthound/checkpoint/idempotency/__init__.py +120 -0
- truthound/checkpoint/idempotency/core.py +295 -0
- truthound/checkpoint/idempotency/fingerprint.py +454 -0
- truthound/checkpoint/idempotency/locking.py +604 -0
- truthound/checkpoint/idempotency/service.py +592 -0
- truthound/checkpoint/idempotency/stores.py +653 -0
- truthound/checkpoint/monitoring/__init__.py +134 -0
- truthound/checkpoint/monitoring/aggregators/__init__.py +15 -0
- truthound/checkpoint/monitoring/aggregators/base.py +372 -0
- truthound/checkpoint/monitoring/aggregators/realtime.py +300 -0
- truthound/checkpoint/monitoring/aggregators/window.py +493 -0
- truthound/checkpoint/monitoring/collectors/__init__.py +17 -0
- truthound/checkpoint/monitoring/collectors/base.py +257 -0
- truthound/checkpoint/monitoring/collectors/memory_collector.py +617 -0
- truthound/checkpoint/monitoring/collectors/prometheus_collector.py +451 -0
- truthound/checkpoint/monitoring/collectors/redis_collector.py +518 -0
- truthound/checkpoint/monitoring/events.py +410 -0
- truthound/checkpoint/monitoring/protocols.py +636 -0
- truthound/checkpoint/monitoring/service.py +578 -0
- truthound/checkpoint/monitoring/views/__init__.py +17 -0
- truthound/checkpoint/monitoring/views/base.py +172 -0
- truthound/checkpoint/monitoring/views/queue_view.py +220 -0
- truthound/checkpoint/monitoring/views/task_view.py +240 -0
- truthound/checkpoint/monitoring/views/worker_view.py +263 -0
- truthound/checkpoint/registry.py +337 -0
- truthound/checkpoint/runner.py +356 -0
- truthound/checkpoint/transaction/__init__.py +133 -0
- truthound/checkpoint/transaction/base.py +389 -0
- truthound/checkpoint/transaction/compensatable.py +537 -0
- truthound/checkpoint/transaction/coordinator.py +576 -0
- truthound/checkpoint/transaction/executor.py +622 -0
- truthound/checkpoint/transaction/idempotency.py +534 -0
- truthound/checkpoint/transaction/saga/__init__.py +143 -0
- truthound/checkpoint/transaction/saga/builder.py +584 -0
- truthound/checkpoint/transaction/saga/definition.py +515 -0
- truthound/checkpoint/transaction/saga/event_store.py +542 -0
- truthound/checkpoint/transaction/saga/patterns.py +833 -0
- truthound/checkpoint/transaction/saga/runner.py +718 -0
- truthound/checkpoint/transaction/saga/state_machine.py +793 -0
- truthound/checkpoint/transaction/saga/strategies.py +780 -0
- truthound/checkpoint/transaction/saga/testing.py +886 -0
- truthound/checkpoint/triggers/__init__.py +58 -0
- truthound/checkpoint/triggers/base.py +237 -0
- truthound/checkpoint/triggers/event.py +385 -0
- truthound/checkpoint/triggers/schedule.py +355 -0
- truthound/cli.py +2358 -0
- truthound/cli_modules/__init__.py +124 -0
- truthound/cli_modules/advanced/__init__.py +45 -0
- truthound/cli_modules/advanced/benchmark.py +343 -0
- truthound/cli_modules/advanced/docs.py +225 -0
- truthound/cli_modules/advanced/lineage.py +209 -0
- truthound/cli_modules/advanced/ml.py +320 -0
- truthound/cli_modules/advanced/realtime.py +196 -0
- truthound/cli_modules/checkpoint/__init__.py +46 -0
- truthound/cli_modules/checkpoint/init.py +114 -0
- truthound/cli_modules/checkpoint/list.py +71 -0
- truthound/cli_modules/checkpoint/run.py +159 -0
- truthound/cli_modules/checkpoint/validate.py +67 -0
- truthound/cli_modules/common/__init__.py +71 -0
- truthound/cli_modules/common/errors.py +414 -0
- truthound/cli_modules/common/options.py +419 -0
- truthound/cli_modules/common/output.py +507 -0
- truthound/cli_modules/common/protocol.py +552 -0
- truthound/cli_modules/core/__init__.py +48 -0
- truthound/cli_modules/core/check.py +123 -0
- truthound/cli_modules/core/compare.py +104 -0
- truthound/cli_modules/core/learn.py +57 -0
- truthound/cli_modules/core/mask.py +77 -0
- truthound/cli_modules/core/profile.py +65 -0
- truthound/cli_modules/core/scan.py +61 -0
- truthound/cli_modules/profiler/__init__.py +51 -0
- truthound/cli_modules/profiler/auto_profile.py +175 -0
- truthound/cli_modules/profiler/metadata.py +107 -0
- truthound/cli_modules/profiler/suite.py +283 -0
- truthound/cli_modules/registry.py +431 -0
- truthound/cli_modules/scaffolding/__init__.py +89 -0
- truthound/cli_modules/scaffolding/base.py +631 -0
- truthound/cli_modules/scaffolding/commands.py +545 -0
- truthound/cli_modules/scaffolding/plugins.py +1072 -0
- truthound/cli_modules/scaffolding/reporters.py +594 -0
- truthound/cli_modules/scaffolding/validators.py +1127 -0
- truthound/common/__init__.py +18 -0
- truthound/common/resilience/__init__.py +130 -0
- truthound/common/resilience/bulkhead.py +266 -0
- truthound/common/resilience/circuit_breaker.py +516 -0
- truthound/common/resilience/composite.py +332 -0
- truthound/common/resilience/config.py +292 -0
- truthound/common/resilience/protocols.py +217 -0
- truthound/common/resilience/rate_limiter.py +404 -0
- truthound/common/resilience/retry.py +341 -0
- truthound/datadocs/__init__.py +260 -0
- truthound/datadocs/base.py +571 -0
- truthound/datadocs/builder.py +761 -0
- truthound/datadocs/charts.py +764 -0
- truthound/datadocs/dashboard/__init__.py +63 -0
- truthound/datadocs/dashboard/app.py +576 -0
- truthound/datadocs/dashboard/components.py +584 -0
- truthound/datadocs/dashboard/state.py +240 -0
- truthound/datadocs/engine/__init__.py +46 -0
- truthound/datadocs/engine/context.py +376 -0
- truthound/datadocs/engine/pipeline.py +618 -0
- truthound/datadocs/engine/registry.py +469 -0
- truthound/datadocs/exporters/__init__.py +49 -0
- truthound/datadocs/exporters/base.py +198 -0
- truthound/datadocs/exporters/html.py +178 -0
- truthound/datadocs/exporters/json_exporter.py +253 -0
- truthound/datadocs/exporters/markdown.py +284 -0
- truthound/datadocs/exporters/pdf.py +392 -0
- truthound/datadocs/i18n/__init__.py +86 -0
- truthound/datadocs/i18n/catalog.py +960 -0
- truthound/datadocs/i18n/formatting.py +505 -0
- truthound/datadocs/i18n/loader.py +256 -0
- truthound/datadocs/i18n/plurals.py +378 -0
- truthound/datadocs/renderers/__init__.py +42 -0
- truthound/datadocs/renderers/base.py +401 -0
- truthound/datadocs/renderers/custom.py +342 -0
- truthound/datadocs/renderers/jinja.py +697 -0
- truthound/datadocs/sections.py +736 -0
- truthound/datadocs/styles.py +931 -0
- truthound/datadocs/themes/__init__.py +101 -0
- truthound/datadocs/themes/base.py +336 -0
- truthound/datadocs/themes/default.py +417 -0
- truthound/datadocs/themes/enterprise.py +419 -0
- truthound/datadocs/themes/loader.py +336 -0
- truthound/datadocs/themes.py +301 -0
- truthound/datadocs/transformers/__init__.py +57 -0
- truthound/datadocs/transformers/base.py +268 -0
- truthound/datadocs/transformers/enrichers.py +544 -0
- truthound/datadocs/transformers/filters.py +447 -0
- truthound/datadocs/transformers/i18n.py +468 -0
- truthound/datadocs/versioning/__init__.py +62 -0
- truthound/datadocs/versioning/diff.py +639 -0
- truthound/datadocs/versioning/storage.py +497 -0
- truthound/datadocs/versioning/version.py +358 -0
- truthound/datasources/__init__.py +223 -0
- truthound/datasources/_async_protocols.py +222 -0
- truthound/datasources/_protocols.py +159 -0
- truthound/datasources/adapters.py +428 -0
- truthound/datasources/async_base.py +599 -0
- truthound/datasources/async_factory.py +511 -0
- truthound/datasources/base.py +516 -0
- truthound/datasources/factory.py +433 -0
- truthound/datasources/nosql/__init__.py +47 -0
- truthound/datasources/nosql/base.py +487 -0
- truthound/datasources/nosql/elasticsearch.py +801 -0
- truthound/datasources/nosql/mongodb.py +636 -0
- truthound/datasources/pandas_optimized.py +582 -0
- truthound/datasources/pandas_source.py +216 -0
- truthound/datasources/polars_source.py +395 -0
- truthound/datasources/spark_source.py +479 -0
- truthound/datasources/sql/__init__.py +154 -0
- truthound/datasources/sql/base.py +710 -0
- truthound/datasources/sql/bigquery.py +410 -0
- truthound/datasources/sql/cloud_base.py +199 -0
- truthound/datasources/sql/databricks.py +471 -0
- truthound/datasources/sql/mysql.py +316 -0
- truthound/datasources/sql/oracle.py +427 -0
- truthound/datasources/sql/postgresql.py +321 -0
- truthound/datasources/sql/redshift.py +479 -0
- truthound/datasources/sql/snowflake.py +439 -0
- truthound/datasources/sql/sqlite.py +286 -0
- truthound/datasources/sql/sqlserver.py +437 -0
- truthound/datasources/streaming/__init__.py +47 -0
- truthound/datasources/streaming/base.py +350 -0
- truthound/datasources/streaming/kafka.py +670 -0
- truthound/decorators.py +98 -0
- truthound/docs/__init__.py +69 -0
- truthound/docs/extractor.py +971 -0
- truthound/docs/generator.py +601 -0
- truthound/docs/parser.py +1037 -0
- truthound/docs/renderer.py +999 -0
- truthound/drift/__init__.py +22 -0
- truthound/drift/compare.py +189 -0
- truthound/drift/detectors.py +464 -0
- truthound/drift/report.py +160 -0
- truthound/execution/__init__.py +65 -0
- truthound/execution/_protocols.py +324 -0
- truthound/execution/base.py +576 -0
- truthound/execution/distributed/__init__.py +179 -0
- truthound/execution/distributed/aggregations.py +731 -0
- truthound/execution/distributed/arrow_bridge.py +817 -0
- truthound/execution/distributed/base.py +550 -0
- truthound/execution/distributed/dask_engine.py +976 -0
- truthound/execution/distributed/mixins.py +766 -0
- truthound/execution/distributed/protocols.py +756 -0
- truthound/execution/distributed/ray_engine.py +1127 -0
- truthound/execution/distributed/registry.py +446 -0
- truthound/execution/distributed/spark_engine.py +1011 -0
- truthound/execution/distributed/validator_adapter.py +682 -0
- truthound/execution/pandas_engine.py +401 -0
- truthound/execution/polars_engine.py +497 -0
- truthound/execution/pushdown/__init__.py +230 -0
- truthound/execution/pushdown/ast.py +1550 -0
- truthound/execution/pushdown/builder.py +1550 -0
- truthound/execution/pushdown/dialects.py +1072 -0
- truthound/execution/pushdown/executor.py +829 -0
- truthound/execution/pushdown/optimizer.py +1041 -0
- truthound/execution/sql_engine.py +518 -0
- truthound/infrastructure/__init__.py +189 -0
- truthound/infrastructure/audit.py +1515 -0
- truthound/infrastructure/config.py +1133 -0
- truthound/infrastructure/encryption.py +1132 -0
- truthound/infrastructure/logging.py +1503 -0
- truthound/infrastructure/metrics.py +1220 -0
- truthound/lineage/__init__.py +89 -0
- truthound/lineage/base.py +746 -0
- truthound/lineage/impact_analysis.py +474 -0
- truthound/lineage/integrations/__init__.py +22 -0
- truthound/lineage/integrations/openlineage.py +548 -0
- truthound/lineage/tracker.py +512 -0
- truthound/lineage/visualization/__init__.py +33 -0
- truthound/lineage/visualization/protocols.py +145 -0
- truthound/lineage/visualization/renderers/__init__.py +20 -0
- truthound/lineage/visualization/renderers/cytoscape.py +329 -0
- truthound/lineage/visualization/renderers/d3.py +331 -0
- truthound/lineage/visualization/renderers/graphviz.py +276 -0
- truthound/lineage/visualization/renderers/mermaid.py +308 -0
- truthound/maskers.py +113 -0
- truthound/ml/__init__.py +124 -0
- truthound/ml/anomaly_models/__init__.py +31 -0
- truthound/ml/anomaly_models/ensemble.py +362 -0
- truthound/ml/anomaly_models/isolation_forest.py +444 -0
- truthound/ml/anomaly_models/statistical.py +392 -0
- truthound/ml/base.py +1178 -0
- truthound/ml/drift_detection/__init__.py +26 -0
- truthound/ml/drift_detection/concept.py +381 -0
- truthound/ml/drift_detection/distribution.py +361 -0
- truthound/ml/drift_detection/feature.py +442 -0
- truthound/ml/drift_detection/multivariate.py +495 -0
- truthound/ml/monitoring/__init__.py +88 -0
- truthound/ml/monitoring/alerting/__init__.py +33 -0
- truthound/ml/monitoring/alerting/handlers.py +427 -0
- truthound/ml/monitoring/alerting/rules.py +508 -0
- truthound/ml/monitoring/collectors/__init__.py +19 -0
- truthound/ml/monitoring/collectors/composite.py +105 -0
- truthound/ml/monitoring/collectors/drift.py +324 -0
- truthound/ml/monitoring/collectors/performance.py +179 -0
- truthound/ml/monitoring/collectors/quality.py +369 -0
- truthound/ml/monitoring/monitor.py +536 -0
- truthound/ml/monitoring/protocols.py +451 -0
- truthound/ml/monitoring/stores/__init__.py +15 -0
- truthound/ml/monitoring/stores/memory.py +201 -0
- truthound/ml/monitoring/stores/prometheus.py +296 -0
- truthound/ml/rule_learning/__init__.py +25 -0
- truthound/ml/rule_learning/constraint_miner.py +443 -0
- truthound/ml/rule_learning/pattern_learner.py +499 -0
- truthound/ml/rule_learning/profile_learner.py +462 -0
- truthound/multitenancy/__init__.py +326 -0
- truthound/multitenancy/core.py +852 -0
- truthound/multitenancy/integration.py +597 -0
- truthound/multitenancy/isolation.py +630 -0
- truthound/multitenancy/manager.py +770 -0
- truthound/multitenancy/middleware.py +765 -0
- truthound/multitenancy/quota.py +537 -0
- truthound/multitenancy/resolvers.py +603 -0
- truthound/multitenancy/storage.py +703 -0
- truthound/observability/__init__.py +307 -0
- truthound/observability/context.py +531 -0
- truthound/observability/instrumentation.py +611 -0
- truthound/observability/logging.py +887 -0
- truthound/observability/metrics.py +1157 -0
- truthound/observability/tracing/__init__.py +178 -0
- truthound/observability/tracing/baggage.py +310 -0
- truthound/observability/tracing/config.py +426 -0
- truthound/observability/tracing/exporter.py +787 -0
- truthound/observability/tracing/integration.py +1018 -0
- truthound/observability/tracing/otel/__init__.py +146 -0
- truthound/observability/tracing/otel/adapter.py +982 -0
- truthound/observability/tracing/otel/bridge.py +1177 -0
- truthound/observability/tracing/otel/compat.py +681 -0
- truthound/observability/tracing/otel/config.py +691 -0
- truthound/observability/tracing/otel/detection.py +327 -0
- truthound/observability/tracing/otel/protocols.py +426 -0
- truthound/observability/tracing/processor.py +561 -0
- truthound/observability/tracing/propagator.py +757 -0
- truthound/observability/tracing/provider.py +569 -0
- truthound/observability/tracing/resource.py +515 -0
- truthound/observability/tracing/sampler.py +487 -0
- truthound/observability/tracing/span.py +676 -0
- truthound/plugins/__init__.py +198 -0
- truthound/plugins/base.py +599 -0
- truthound/plugins/cli.py +680 -0
- truthound/plugins/dependencies/__init__.py +42 -0
- truthound/plugins/dependencies/graph.py +422 -0
- truthound/plugins/dependencies/resolver.py +417 -0
- truthound/plugins/discovery.py +379 -0
- truthound/plugins/docs/__init__.py +46 -0
- truthound/plugins/docs/extractor.py +444 -0
- truthound/plugins/docs/renderer.py +499 -0
- truthound/plugins/enterprise_manager.py +877 -0
- truthound/plugins/examples/__init__.py +19 -0
- truthound/plugins/examples/custom_validators.py +317 -0
- truthound/plugins/examples/slack_notifier.py +312 -0
- truthound/plugins/examples/xml_reporter.py +254 -0
- truthound/plugins/hooks.py +558 -0
- truthound/plugins/lifecycle/__init__.py +43 -0
- truthound/plugins/lifecycle/hot_reload.py +402 -0
- truthound/plugins/lifecycle/manager.py +371 -0
- truthound/plugins/manager.py +736 -0
- truthound/plugins/registry.py +338 -0
- truthound/plugins/security/__init__.py +93 -0
- truthound/plugins/security/exceptions.py +332 -0
- truthound/plugins/security/policies.py +348 -0
- truthound/plugins/security/protocols.py +643 -0
- truthound/plugins/security/sandbox/__init__.py +45 -0
- truthound/plugins/security/sandbox/context.py +158 -0
- truthound/plugins/security/sandbox/engines/__init__.py +19 -0
- truthound/plugins/security/sandbox/engines/container.py +379 -0
- truthound/plugins/security/sandbox/engines/noop.py +144 -0
- truthound/plugins/security/sandbox/engines/process.py +336 -0
- truthound/plugins/security/sandbox/factory.py +211 -0
- truthound/plugins/security/signing/__init__.py +57 -0
- truthound/plugins/security/signing/service.py +330 -0
- truthound/plugins/security/signing/trust_store.py +368 -0
- truthound/plugins/security/signing/verifier.py +459 -0
- truthound/plugins/versioning/__init__.py +41 -0
- truthound/plugins/versioning/constraints.py +297 -0
- truthound/plugins/versioning/resolver.py +329 -0
- truthound/profiler/__init__.py +1729 -0
- truthound/profiler/_lazy.py +452 -0
- truthound/profiler/ab_testing/__init__.py +80 -0
- truthound/profiler/ab_testing/analysis.py +449 -0
- truthound/profiler/ab_testing/base.py +257 -0
- truthound/profiler/ab_testing/experiment.py +395 -0
- truthound/profiler/ab_testing/tracking.py +368 -0
- truthound/profiler/auto_threshold.py +1170 -0
- truthound/profiler/base.py +579 -0
- truthound/profiler/cache_patterns.py +911 -0
- truthound/profiler/caching.py +1303 -0
- truthound/profiler/column_profiler.py +712 -0
- truthound/profiler/comparison.py +1007 -0
- truthound/profiler/custom_patterns.py +1170 -0
- truthound/profiler/dashboard/__init__.py +50 -0
- truthound/profiler/dashboard/app.py +476 -0
- truthound/profiler/dashboard/components.py +457 -0
- truthound/profiler/dashboard/config.py +72 -0
- truthound/profiler/distributed/__init__.py +83 -0
- truthound/profiler/distributed/base.py +281 -0
- truthound/profiler/distributed/dask_backend.py +498 -0
- truthound/profiler/distributed/local_backend.py +293 -0
- truthound/profiler/distributed/profiler.py +304 -0
- truthound/profiler/distributed/ray_backend.py +374 -0
- truthound/profiler/distributed/spark_backend.py +375 -0
- truthound/profiler/distributed.py +1366 -0
- truthound/profiler/enterprise_sampling.py +1065 -0
- truthound/profiler/errors.py +488 -0
- truthound/profiler/evolution/__init__.py +91 -0
- truthound/profiler/evolution/alerts.py +426 -0
- truthound/profiler/evolution/changes.py +206 -0
- truthound/profiler/evolution/compatibility.py +365 -0
- truthound/profiler/evolution/detector.py +372 -0
- truthound/profiler/evolution/protocols.py +121 -0
- truthound/profiler/generators/__init__.py +48 -0
- truthound/profiler/generators/base.py +384 -0
- truthound/profiler/generators/ml_rules.py +375 -0
- truthound/profiler/generators/pattern_rules.py +384 -0
- truthound/profiler/generators/schema_rules.py +267 -0
- truthound/profiler/generators/stats_rules.py +324 -0
- truthound/profiler/generators/suite_generator.py +857 -0
- truthound/profiler/i18n.py +1542 -0
- truthound/profiler/incremental.py +554 -0
- truthound/profiler/incremental_validation.py +1710 -0
- truthound/profiler/integration/__init__.py +73 -0
- truthound/profiler/integration/adapters.py +345 -0
- truthound/profiler/integration/context.py +371 -0
- truthound/profiler/integration/executor.py +527 -0
- truthound/profiler/integration/naming.py +75 -0
- truthound/profiler/integration/protocols.py +243 -0
- truthound/profiler/memory.py +1185 -0
- truthound/profiler/migration/__init__.py +60 -0
- truthound/profiler/migration/base.py +345 -0
- truthound/profiler/migration/manager.py +444 -0
- truthound/profiler/migration/v1_0_to_v1_1.py +484 -0
- truthound/profiler/ml/__init__.py +73 -0
- truthound/profiler/ml/base.py +244 -0
- truthound/profiler/ml/classifier.py +507 -0
- truthound/profiler/ml/feature_extraction.py +604 -0
- truthound/profiler/ml/pretrained.py +448 -0
- truthound/profiler/ml_inference.py +1276 -0
- truthound/profiler/native_patterns.py +815 -0
- truthound/profiler/observability.py +1184 -0
- truthound/profiler/process_timeout.py +1566 -0
- truthound/profiler/progress.py +568 -0
- truthound/profiler/progress_callbacks.py +1734 -0
- truthound/profiler/quality.py +1345 -0
- truthound/profiler/resilience.py +1180 -0
- truthound/profiler/sampled_matcher.py +794 -0
- truthound/profiler/sampling.py +1288 -0
- truthound/profiler/scheduling/__init__.py +82 -0
- truthound/profiler/scheduling/protocols.py +214 -0
- truthound/profiler/scheduling/scheduler.py +474 -0
- truthound/profiler/scheduling/storage.py +457 -0
- truthound/profiler/scheduling/triggers.py +449 -0
- truthound/profiler/schema.py +603 -0
- truthound/profiler/streaming.py +685 -0
- truthound/profiler/streaming_patterns.py +1354 -0
- truthound/profiler/suite_cli.py +625 -0
- truthound/profiler/suite_config.py +789 -0
- truthound/profiler/suite_export.py +1268 -0
- truthound/profiler/table_profiler.py +547 -0
- truthound/profiler/timeout.py +565 -0
- truthound/profiler/validation.py +1532 -0
- truthound/profiler/visualization/__init__.py +118 -0
- truthound/profiler/visualization/base.py +346 -0
- truthound/profiler/visualization/generator.py +1259 -0
- truthound/profiler/visualization/plotly_renderer.py +811 -0
- truthound/profiler/visualization/renderers.py +669 -0
- truthound/profiler/visualization/sections.py +540 -0
- truthound/profiler/visualization.py +2122 -0
- truthound/profiler/yaml_validation.py +1151 -0
- truthound/py.typed +0 -0
- truthound/ratelimit/__init__.py +248 -0
- truthound/ratelimit/algorithms.py +1108 -0
- truthound/ratelimit/core.py +573 -0
- truthound/ratelimit/integration.py +532 -0
- truthound/ratelimit/limiter.py +663 -0
- truthound/ratelimit/middleware.py +700 -0
- truthound/ratelimit/policy.py +792 -0
- truthound/ratelimit/storage.py +763 -0
- truthound/rbac/__init__.py +340 -0
- truthound/rbac/core.py +976 -0
- truthound/rbac/integration.py +760 -0
- truthound/rbac/manager.py +1052 -0
- truthound/rbac/middleware.py +842 -0
- truthound/rbac/policy.py +954 -0
- truthound/rbac/storage.py +878 -0
- truthound/realtime/__init__.py +141 -0
- truthound/realtime/adapters/__init__.py +43 -0
- truthound/realtime/adapters/base.py +533 -0
- truthound/realtime/adapters/kafka.py +487 -0
- truthound/realtime/adapters/kinesis.py +479 -0
- truthound/realtime/adapters/mock.py +243 -0
- truthound/realtime/base.py +553 -0
- truthound/realtime/factory.py +382 -0
- truthound/realtime/incremental.py +660 -0
- truthound/realtime/processing/__init__.py +67 -0
- truthound/realtime/processing/exactly_once.py +575 -0
- truthound/realtime/processing/state.py +547 -0
- truthound/realtime/processing/windows.py +647 -0
- truthound/realtime/protocols.py +569 -0
- truthound/realtime/streaming.py +605 -0
- truthound/realtime/testing/__init__.py +32 -0
- truthound/realtime/testing/containers.py +615 -0
- truthound/realtime/testing/fixtures.py +484 -0
- truthound/report.py +280 -0
- truthound/reporters/__init__.py +46 -0
- truthound/reporters/_protocols.py +30 -0
- truthound/reporters/base.py +324 -0
- truthound/reporters/ci/__init__.py +66 -0
- truthound/reporters/ci/azure.py +436 -0
- truthound/reporters/ci/base.py +509 -0
- truthound/reporters/ci/bitbucket.py +567 -0
- truthound/reporters/ci/circleci.py +547 -0
- truthound/reporters/ci/detection.py +364 -0
- truthound/reporters/ci/factory.py +182 -0
- truthound/reporters/ci/github.py +388 -0
- truthound/reporters/ci/gitlab.py +471 -0
- truthound/reporters/ci/jenkins.py +525 -0
- truthound/reporters/console_reporter.py +299 -0
- truthound/reporters/factory.py +211 -0
- truthound/reporters/html_reporter.py +524 -0
- truthound/reporters/json_reporter.py +256 -0
- truthound/reporters/markdown_reporter.py +280 -0
- truthound/reporters/sdk/__init__.py +174 -0
- truthound/reporters/sdk/builder.py +558 -0
- truthound/reporters/sdk/mixins.py +1150 -0
- truthound/reporters/sdk/schema.py +1493 -0
- truthound/reporters/sdk/templates.py +666 -0
- truthound/reporters/sdk/testing.py +968 -0
- truthound/scanners.py +170 -0
- truthound/scheduling/__init__.py +122 -0
- truthound/scheduling/cron.py +1136 -0
- truthound/scheduling/presets.py +212 -0
- truthound/schema.py +275 -0
- truthound/secrets/__init__.py +173 -0
- truthound/secrets/base.py +618 -0
- truthound/secrets/cloud.py +682 -0
- truthound/secrets/integration.py +507 -0
- truthound/secrets/manager.py +633 -0
- truthound/secrets/oidc/__init__.py +172 -0
- truthound/secrets/oidc/base.py +902 -0
- truthound/secrets/oidc/credential_provider.py +623 -0
- truthound/secrets/oidc/exchangers.py +1001 -0
- truthound/secrets/oidc/github/__init__.py +110 -0
- truthound/secrets/oidc/github/claims.py +718 -0
- truthound/secrets/oidc/github/enhanced_provider.py +693 -0
- truthound/secrets/oidc/github/trust_policy.py +742 -0
- truthound/secrets/oidc/github/verification.py +723 -0
- truthound/secrets/oidc/github/workflow.py +691 -0
- truthound/secrets/oidc/providers.py +825 -0
- truthound/secrets/providers.py +506 -0
- truthound/secrets/resolver.py +495 -0
- truthound/stores/__init__.py +177 -0
- truthound/stores/backends/__init__.py +18 -0
- truthound/stores/backends/_protocols.py +340 -0
- truthound/stores/backends/azure_blob.py +530 -0
- truthound/stores/backends/concurrent_filesystem.py +915 -0
- truthound/stores/backends/connection_pool.py +1365 -0
- truthound/stores/backends/database.py +743 -0
- truthound/stores/backends/filesystem.py +538 -0
- truthound/stores/backends/gcs.py +399 -0
- truthound/stores/backends/memory.py +354 -0
- truthound/stores/backends/s3.py +434 -0
- truthound/stores/backpressure/__init__.py +84 -0
- truthound/stores/backpressure/base.py +375 -0
- truthound/stores/backpressure/circuit_breaker.py +434 -0
- truthound/stores/backpressure/monitor.py +376 -0
- truthound/stores/backpressure/strategies.py +677 -0
- truthound/stores/base.py +551 -0
- truthound/stores/batching/__init__.py +65 -0
- truthound/stores/batching/base.py +305 -0
- truthound/stores/batching/buffer.py +370 -0
- truthound/stores/batching/store.py +248 -0
- truthound/stores/batching/writer.py +521 -0
- truthound/stores/caching/__init__.py +60 -0
- truthound/stores/caching/backends.py +684 -0
- truthound/stores/caching/base.py +356 -0
- truthound/stores/caching/store.py +305 -0
- truthound/stores/compression/__init__.py +193 -0
- truthound/stores/compression/adaptive.py +694 -0
- truthound/stores/compression/base.py +514 -0
- truthound/stores/compression/pipeline.py +868 -0
- truthound/stores/compression/providers.py +672 -0
- truthound/stores/compression/streaming.py +832 -0
- truthound/stores/concurrency/__init__.py +81 -0
- truthound/stores/concurrency/atomic.py +556 -0
- truthound/stores/concurrency/index.py +775 -0
- truthound/stores/concurrency/locks.py +576 -0
- truthound/stores/concurrency/manager.py +482 -0
- truthound/stores/encryption/__init__.py +297 -0
- truthound/stores/encryption/base.py +952 -0
- truthound/stores/encryption/keys.py +1191 -0
- truthound/stores/encryption/pipeline.py +903 -0
- truthound/stores/encryption/providers.py +953 -0
- truthound/stores/encryption/streaming.py +950 -0
- truthound/stores/expectations.py +227 -0
- truthound/stores/factory.py +246 -0
- truthound/stores/migration/__init__.py +75 -0
- truthound/stores/migration/base.py +480 -0
- truthound/stores/migration/manager.py +347 -0
- truthound/stores/migration/registry.py +382 -0
- truthound/stores/migration/store.py +559 -0
- truthound/stores/observability/__init__.py +106 -0
- truthound/stores/observability/audit.py +718 -0
- truthound/stores/observability/config.py +270 -0
- truthound/stores/observability/factory.py +208 -0
- truthound/stores/observability/metrics.py +636 -0
- truthound/stores/observability/protocols.py +410 -0
- truthound/stores/observability/store.py +570 -0
- truthound/stores/observability/tracing.py +784 -0
- truthound/stores/replication/__init__.py +76 -0
- truthound/stores/replication/base.py +260 -0
- truthound/stores/replication/monitor.py +269 -0
- truthound/stores/replication/store.py +439 -0
- truthound/stores/replication/syncer.py +391 -0
- truthound/stores/results.py +359 -0
- truthound/stores/retention/__init__.py +77 -0
- truthound/stores/retention/base.py +378 -0
- truthound/stores/retention/policies.py +621 -0
- truthound/stores/retention/scheduler.py +279 -0
- truthound/stores/retention/store.py +526 -0
- truthound/stores/streaming/__init__.py +138 -0
- truthound/stores/streaming/base.py +801 -0
- truthound/stores/streaming/database.py +984 -0
- truthound/stores/streaming/filesystem.py +719 -0
- truthound/stores/streaming/reader.py +629 -0
- truthound/stores/streaming/s3.py +843 -0
- truthound/stores/streaming/writer.py +790 -0
- truthound/stores/tiering/__init__.py +108 -0
- truthound/stores/tiering/base.py +462 -0
- truthound/stores/tiering/manager.py +249 -0
- truthound/stores/tiering/policies.py +692 -0
- truthound/stores/tiering/store.py +526 -0
- truthound/stores/versioning/__init__.py +56 -0
- truthound/stores/versioning/base.py +376 -0
- truthound/stores/versioning/store.py +660 -0
- truthound/stores/versioning/strategies.py +353 -0
- truthound/types.py +56 -0
- truthound/validators/__init__.py +774 -0
- truthound/validators/aggregate/__init__.py +27 -0
- truthound/validators/aggregate/central.py +116 -0
- truthound/validators/aggregate/extremes.py +116 -0
- truthound/validators/aggregate/spread.py +118 -0
- truthound/validators/aggregate/sum.py +64 -0
- truthound/validators/aggregate/type.py +78 -0
- truthound/validators/anomaly/__init__.py +93 -0
- truthound/validators/anomaly/base.py +431 -0
- truthound/validators/anomaly/ml_based.py +1190 -0
- truthound/validators/anomaly/multivariate.py +647 -0
- truthound/validators/anomaly/statistical.py +599 -0
- truthound/validators/base.py +1089 -0
- truthound/validators/business_rule/__init__.py +46 -0
- truthound/validators/business_rule/base.py +147 -0
- truthound/validators/business_rule/checksum.py +509 -0
- truthound/validators/business_rule/financial.py +526 -0
- truthound/validators/cache.py +733 -0
- truthound/validators/completeness/__init__.py +39 -0
- truthound/validators/completeness/conditional.py +73 -0
- truthound/validators/completeness/default.py +98 -0
- truthound/validators/completeness/empty.py +103 -0
- truthound/validators/completeness/nan.py +337 -0
- truthound/validators/completeness/null.py +152 -0
- truthound/validators/cross_table/__init__.py +17 -0
- truthound/validators/cross_table/aggregate.py +333 -0
- truthound/validators/cross_table/row_count.py +122 -0
- truthound/validators/datetime/__init__.py +29 -0
- truthound/validators/datetime/format.py +78 -0
- truthound/validators/datetime/freshness.py +269 -0
- truthound/validators/datetime/order.py +73 -0
- truthound/validators/datetime/parseable.py +185 -0
- truthound/validators/datetime/range.py +202 -0
- truthound/validators/datetime/timezone.py +69 -0
- truthound/validators/distribution/__init__.py +49 -0
- truthound/validators/distribution/distribution.py +128 -0
- truthound/validators/distribution/monotonic.py +119 -0
- truthound/validators/distribution/outlier.py +178 -0
- truthound/validators/distribution/quantile.py +80 -0
- truthound/validators/distribution/range.py +254 -0
- truthound/validators/distribution/set.py +125 -0
- truthound/validators/distribution/statistical.py +459 -0
- truthound/validators/drift/__init__.py +79 -0
- truthound/validators/drift/base.py +427 -0
- truthound/validators/drift/multi_feature.py +401 -0
- truthound/validators/drift/numeric.py +395 -0
- truthound/validators/drift/psi.py +446 -0
- truthound/validators/drift/statistical.py +510 -0
- truthound/validators/enterprise.py +1658 -0
- truthound/validators/geospatial/__init__.py +80 -0
- truthound/validators/geospatial/base.py +97 -0
- truthound/validators/geospatial/boundary.py +238 -0
- truthound/validators/geospatial/coordinate.py +351 -0
- truthound/validators/geospatial/distance.py +399 -0
- truthound/validators/geospatial/polygon.py +665 -0
- truthound/validators/i18n/__init__.py +308 -0
- truthound/validators/i18n/bidi.py +571 -0
- truthound/validators/i18n/catalogs.py +570 -0
- truthound/validators/i18n/dialects.py +763 -0
- truthound/validators/i18n/extended_catalogs.py +549 -0
- truthound/validators/i18n/formatting.py +1434 -0
- truthound/validators/i18n/loader.py +1020 -0
- truthound/validators/i18n/messages.py +521 -0
- truthound/validators/i18n/plural.py +683 -0
- truthound/validators/i18n/protocols.py +855 -0
- truthound/validators/i18n/tms.py +1162 -0
- truthound/validators/localization/__init__.py +53 -0
- truthound/validators/localization/base.py +122 -0
- truthound/validators/localization/chinese.py +362 -0
- truthound/validators/localization/japanese.py +275 -0
- truthound/validators/localization/korean.py +524 -0
- truthound/validators/memory/__init__.py +94 -0
- truthound/validators/memory/approximate_knn.py +506 -0
- truthound/validators/memory/base.py +547 -0
- truthound/validators/memory/sgd_online.py +719 -0
- truthound/validators/memory/streaming_ecdf.py +753 -0
- truthound/validators/ml_feature/__init__.py +54 -0
- truthound/validators/ml_feature/base.py +249 -0
- truthound/validators/ml_feature/correlation.py +299 -0
- truthound/validators/ml_feature/leakage.py +344 -0
- truthound/validators/ml_feature/null_impact.py +270 -0
- truthound/validators/ml_feature/scale.py +264 -0
- truthound/validators/multi_column/__init__.py +89 -0
- truthound/validators/multi_column/arithmetic.py +284 -0
- truthound/validators/multi_column/base.py +231 -0
- truthound/validators/multi_column/comparison.py +273 -0
- truthound/validators/multi_column/consistency.py +312 -0
- truthound/validators/multi_column/statistical.py +299 -0
- truthound/validators/optimization/__init__.py +164 -0
- truthound/validators/optimization/aggregation.py +563 -0
- truthound/validators/optimization/covariance.py +556 -0
- truthound/validators/optimization/geo.py +626 -0
- truthound/validators/optimization/graph.py +587 -0
- truthound/validators/optimization/orchestrator.py +970 -0
- truthound/validators/optimization/profiling.py +1312 -0
- truthound/validators/privacy/__init__.py +223 -0
- truthound/validators/privacy/base.py +635 -0
- truthound/validators/privacy/ccpa.py +670 -0
- truthound/validators/privacy/gdpr.py +728 -0
- truthound/validators/privacy/global_patterns.py +604 -0
- truthound/validators/privacy/plugins.py +867 -0
- truthound/validators/profiling/__init__.py +52 -0
- truthound/validators/profiling/base.py +175 -0
- truthound/validators/profiling/cardinality.py +312 -0
- truthound/validators/profiling/entropy.py +391 -0
- truthound/validators/profiling/frequency.py +455 -0
- truthound/validators/pushdown_support.py +660 -0
- truthound/validators/query/__init__.py +91 -0
- truthound/validators/query/aggregate.py +346 -0
- truthound/validators/query/base.py +246 -0
- truthound/validators/query/column.py +249 -0
- truthound/validators/query/expression.py +274 -0
- truthound/validators/query/result.py +323 -0
- truthound/validators/query/row_count.py +264 -0
- truthound/validators/referential/__init__.py +80 -0
- truthound/validators/referential/base.py +395 -0
- truthound/validators/referential/cascade.py +391 -0
- truthound/validators/referential/circular.py +563 -0
- truthound/validators/referential/foreign_key.py +624 -0
- truthound/validators/referential/orphan.py +485 -0
- truthound/validators/registry.py +112 -0
- truthound/validators/schema/__init__.py +41 -0
- truthound/validators/schema/column_count.py +142 -0
- truthound/validators/schema/column_exists.py +80 -0
- truthound/validators/schema/column_order.py +82 -0
- truthound/validators/schema/column_pair.py +85 -0
- truthound/validators/schema/column_pair_set.py +195 -0
- truthound/validators/schema/column_type.py +94 -0
- truthound/validators/schema/multi_column.py +53 -0
- truthound/validators/schema/multi_column_aggregate.py +175 -0
- truthound/validators/schema/referential.py +274 -0
- truthound/validators/schema/table_schema.py +91 -0
- truthound/validators/schema_validator.py +219 -0
- truthound/validators/sdk/__init__.py +250 -0
- truthound/validators/sdk/builder.py +680 -0
- truthound/validators/sdk/decorators.py +474 -0
- truthound/validators/sdk/enterprise/__init__.py +211 -0
- truthound/validators/sdk/enterprise/docs.py +725 -0
- truthound/validators/sdk/enterprise/fuzzing.py +659 -0
- truthound/validators/sdk/enterprise/licensing.py +709 -0
- truthound/validators/sdk/enterprise/manager.py +543 -0
- truthound/validators/sdk/enterprise/resources.py +628 -0
- truthound/validators/sdk/enterprise/sandbox.py +766 -0
- truthound/validators/sdk/enterprise/signing.py +603 -0
- truthound/validators/sdk/enterprise/templates.py +865 -0
- truthound/validators/sdk/enterprise/versioning.py +659 -0
- truthound/validators/sdk/templates.py +757 -0
- truthound/validators/sdk/testing.py +807 -0
- truthound/validators/security/__init__.py +181 -0
- truthound/validators/security/redos/__init__.py +182 -0
- truthound/validators/security/redos/core.py +861 -0
- truthound/validators/security/redos/cpu_monitor.py +593 -0
- truthound/validators/security/redos/cve_database.py +791 -0
- truthound/validators/security/redos/ml/__init__.py +155 -0
- truthound/validators/security/redos/ml/base.py +785 -0
- truthound/validators/security/redos/ml/datasets.py +618 -0
- truthound/validators/security/redos/ml/features.py +359 -0
- truthound/validators/security/redos/ml/models.py +1000 -0
- truthound/validators/security/redos/ml/predictor.py +507 -0
- truthound/validators/security/redos/ml/storage.py +632 -0
- truthound/validators/security/redos/ml/training.py +571 -0
- truthound/validators/security/redos/ml_analyzer.py +937 -0
- truthound/validators/security/redos/optimizer.py +674 -0
- truthound/validators/security/redos/profiler.py +682 -0
- truthound/validators/security/redos/re2_engine.py +709 -0
- truthound/validators/security/redos.py +886 -0
- truthound/validators/security/sql_security.py +1247 -0
- truthound/validators/streaming/__init__.py +126 -0
- truthound/validators/streaming/base.py +292 -0
- truthound/validators/streaming/completeness.py +210 -0
- truthound/validators/streaming/mixin.py +575 -0
- truthound/validators/streaming/range.py +308 -0
- truthound/validators/streaming/sources.py +846 -0
- truthound/validators/string/__init__.py +57 -0
- truthound/validators/string/casing.py +158 -0
- truthound/validators/string/charset.py +96 -0
- truthound/validators/string/format.py +501 -0
- truthound/validators/string/json.py +77 -0
- truthound/validators/string/json_schema.py +184 -0
- truthound/validators/string/length.py +104 -0
- truthound/validators/string/like_pattern.py +237 -0
- truthound/validators/string/regex.py +202 -0
- truthound/validators/string/regex_extended.py +435 -0
- truthound/validators/table/__init__.py +88 -0
- truthound/validators/table/base.py +78 -0
- truthound/validators/table/column_count.py +198 -0
- truthound/validators/table/freshness.py +362 -0
- truthound/validators/table/row_count.py +251 -0
- truthound/validators/table/schema.py +333 -0
- truthound/validators/table/size.py +285 -0
- truthound/validators/timeout/__init__.py +102 -0
- truthound/validators/timeout/advanced/__init__.py +247 -0
- truthound/validators/timeout/advanced/circuit_breaker.py +675 -0
- truthound/validators/timeout/advanced/prediction.py +773 -0
- truthound/validators/timeout/advanced/priority.py +618 -0
- truthound/validators/timeout/advanced/redis_backend.py +770 -0
- truthound/validators/timeout/advanced/retry.py +721 -0
- truthound/validators/timeout/advanced/sampling.py +788 -0
- truthound/validators/timeout/advanced/sla.py +661 -0
- truthound/validators/timeout/advanced/telemetry.py +804 -0
- truthound/validators/timeout/cascade.py +477 -0
- truthound/validators/timeout/deadline.py +657 -0
- truthound/validators/timeout/degradation.py +525 -0
- truthound/validators/timeout/distributed.py +597 -0
- truthound/validators/timeseries/__init__.py +89 -0
- truthound/validators/timeseries/base.py +326 -0
- truthound/validators/timeseries/completeness.py +617 -0
- truthound/validators/timeseries/gap.py +485 -0
- truthound/validators/timeseries/monotonic.py +310 -0
- truthound/validators/timeseries/seasonality.py +422 -0
- truthound/validators/timeseries/trend.py +510 -0
- truthound/validators/uniqueness/__init__.py +59 -0
- truthound/validators/uniqueness/approximate.py +475 -0
- truthound/validators/uniqueness/distinct_values.py +253 -0
- truthound/validators/uniqueness/duplicate.py +118 -0
- truthound/validators/uniqueness/primary_key.py +140 -0
- truthound/validators/uniqueness/unique.py +191 -0
- truthound/validators/uniqueness/within_record.py +599 -0
- truthound/validators/utils.py +756 -0
- truthound-1.0.8.dist-info/METADATA +474 -0
- truthound-1.0.8.dist-info/RECORD +877 -0
- truthound-1.0.8.dist-info/WHEEL +4 -0
- truthound-1.0.8.dist-info/entry_points.txt +2 -0
- truthound-1.0.8.dist-info/licenses/LICENSE +190 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""Plugin signing service implementation.
|
|
2
|
+
|
|
3
|
+
This module provides the SigningServiceImpl that can sign plugins
|
|
4
|
+
using various cryptographic algorithms.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import base64
|
|
10
|
+
import hashlib
|
|
11
|
+
import hmac
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
from datetime import datetime, timedelta, timezone
|
|
15
|
+
from enum import Enum, auto
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from truthound.plugins.security.protocols import SignatureInfo, VerificationResult, TrustLevel
|
|
20
|
+
from truthound.plugins.security.exceptions import SignatureError, InvalidSignatureError
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SignatureAlgorithm(Enum):
|
|
26
|
+
"""Supported signature algorithms."""
|
|
27
|
+
|
|
28
|
+
SHA256 = auto() # Simple SHA256 hash (not recommended for production)
|
|
29
|
+
SHA512 = auto() # SHA512 hash
|
|
30
|
+
HMAC_SHA256 = auto() # HMAC with SHA256
|
|
31
|
+
HMAC_SHA512 = auto() # HMAC with SHA512
|
|
32
|
+
RSA_SHA256 = auto() # RSA with SHA256 (requires cryptography)
|
|
33
|
+
ED25519 = auto() # Ed25519 (requires cryptography)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SigningServiceImpl:
|
|
37
|
+
"""Implementation of plugin signing service.
|
|
38
|
+
|
|
39
|
+
Supports multiple signature algorithms from simple hashing to
|
|
40
|
+
cryptographic signatures.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> service = SigningServiceImpl(algorithm=SignatureAlgorithm.HMAC_SHA256)
|
|
44
|
+
>>> signature = service.sign(
|
|
45
|
+
... plugin_path=Path("my_plugin"),
|
|
46
|
+
... private_key=b"secret_key",
|
|
47
|
+
... )
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
algorithm: SignatureAlgorithm = SignatureAlgorithm.SHA256,
|
|
53
|
+
signer_id: str = "truthound",
|
|
54
|
+
validity_days: int = 365,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Initialize signing service.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
algorithm: Signature algorithm to use
|
|
60
|
+
signer_id: Identifier for this signer
|
|
61
|
+
validity_days: How long signatures are valid
|
|
62
|
+
"""
|
|
63
|
+
self.algorithm = algorithm
|
|
64
|
+
self.signer_id = signer_id
|
|
65
|
+
self.validity_days = validity_days
|
|
66
|
+
|
|
67
|
+
def sign(
|
|
68
|
+
self,
|
|
69
|
+
plugin_path: Path,
|
|
70
|
+
private_key: bytes,
|
|
71
|
+
certificate: bytes | None = None,
|
|
72
|
+
metadata: dict[str, Any] | None = None,
|
|
73
|
+
) -> SignatureInfo:
|
|
74
|
+
"""Sign a plugin.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
plugin_path: Path to plugin file or directory
|
|
78
|
+
private_key: Private key or secret for signing
|
|
79
|
+
certificate: Optional X.509 certificate
|
|
80
|
+
metadata: Additional metadata
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
SignatureInfo with signature details
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
SignatureError: If signing fails
|
|
87
|
+
"""
|
|
88
|
+
# Get plugin hash
|
|
89
|
+
content_hash = self.get_plugin_hash(plugin_path)
|
|
90
|
+
|
|
91
|
+
# Build data to sign
|
|
92
|
+
timestamp = datetime.now(timezone.utc)
|
|
93
|
+
expires_at = timestamp + timedelta(days=self.validity_days)
|
|
94
|
+
|
|
95
|
+
sign_data = {
|
|
96
|
+
"plugin_hash": content_hash,
|
|
97
|
+
"signer_id": self.signer_id,
|
|
98
|
+
"algorithm": self.algorithm.name,
|
|
99
|
+
"timestamp": timestamp.isoformat(),
|
|
100
|
+
"expires_at": expires_at.isoformat(),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Create signature
|
|
104
|
+
data_bytes = json.dumps(sign_data, sort_keys=True).encode()
|
|
105
|
+
signature = self._create_signature(data_bytes, private_key)
|
|
106
|
+
|
|
107
|
+
return SignatureInfo(
|
|
108
|
+
signer_id=self.signer_id,
|
|
109
|
+
algorithm=self.algorithm.name,
|
|
110
|
+
signature=signature,
|
|
111
|
+
timestamp=timestamp,
|
|
112
|
+
expires_at=expires_at,
|
|
113
|
+
certificate_chain=(certificate,) if certificate else (),
|
|
114
|
+
metadata={
|
|
115
|
+
"plugin_hash": content_hash,
|
|
116
|
+
**(metadata or {}),
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def verify(
|
|
121
|
+
self,
|
|
122
|
+
plugin_path: Path,
|
|
123
|
+
signature: SignatureInfo,
|
|
124
|
+
) -> VerificationResult:
|
|
125
|
+
"""Verify a plugin signature.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
plugin_path: Path to plugin
|
|
129
|
+
signature: Signature to verify
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
VerificationResult with verification status
|
|
133
|
+
"""
|
|
134
|
+
errors: list[str] = []
|
|
135
|
+
warnings: list[str] = []
|
|
136
|
+
|
|
137
|
+
# Check expiration
|
|
138
|
+
if signature.is_expired():
|
|
139
|
+
errors.append(f"Signature expired at {signature.expires_at}")
|
|
140
|
+
return VerificationResult.failure(*errors)
|
|
141
|
+
|
|
142
|
+
# Check hash
|
|
143
|
+
current_hash = self.get_plugin_hash(plugin_path)
|
|
144
|
+
stored_hash = signature.metadata.get("plugin_hash", "")
|
|
145
|
+
|
|
146
|
+
if current_hash != stored_hash:
|
|
147
|
+
errors.append("Plugin content has been modified")
|
|
148
|
+
return VerificationResult.failure(*errors)
|
|
149
|
+
|
|
150
|
+
# Verify signature age warning
|
|
151
|
+
if signature.age_days > 180:
|
|
152
|
+
warnings.append(f"Signature is {signature.age_days} days old")
|
|
153
|
+
|
|
154
|
+
return VerificationResult.success(
|
|
155
|
+
signer_id=signature.signer_id,
|
|
156
|
+
trust_level=TrustLevel.VERIFIED,
|
|
157
|
+
warnings=tuple(warnings),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def get_plugin_hash(self, plugin_path: Path) -> str:
|
|
161
|
+
"""Get hash of plugin content.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
plugin_path: Path to plugin file or directory
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Hex-encoded SHA256 hash
|
|
168
|
+
"""
|
|
169
|
+
hasher = hashlib.sha256()
|
|
170
|
+
|
|
171
|
+
if plugin_path.is_file():
|
|
172
|
+
# Hash single file
|
|
173
|
+
with open(plugin_path, "rb") as f:
|
|
174
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
175
|
+
hasher.update(chunk)
|
|
176
|
+
elif plugin_path.is_dir():
|
|
177
|
+
# Hash all Python files in directory
|
|
178
|
+
for py_file in sorted(plugin_path.rglob("*.py")):
|
|
179
|
+
# Add relative path to hash for file identity
|
|
180
|
+
rel_path = py_file.relative_to(plugin_path)
|
|
181
|
+
hasher.update(str(rel_path).encode())
|
|
182
|
+
with open(py_file, "rb") as f:
|
|
183
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
184
|
+
hasher.update(chunk)
|
|
185
|
+
else:
|
|
186
|
+
raise SignatureError(f"Plugin path does not exist: {plugin_path}")
|
|
187
|
+
|
|
188
|
+
return hasher.hexdigest()
|
|
189
|
+
|
|
190
|
+
def _create_signature(
|
|
191
|
+
self,
|
|
192
|
+
data: bytes,
|
|
193
|
+
private_key: bytes,
|
|
194
|
+
) -> bytes:
|
|
195
|
+
"""Create cryptographic signature.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
data: Data to sign
|
|
199
|
+
private_key: Private key or secret
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Signature bytes
|
|
203
|
+
"""
|
|
204
|
+
if self.algorithm == SignatureAlgorithm.SHA256:
|
|
205
|
+
return hashlib.sha256(data).digest()
|
|
206
|
+
|
|
207
|
+
elif self.algorithm == SignatureAlgorithm.SHA512:
|
|
208
|
+
return hashlib.sha512(data).digest()
|
|
209
|
+
|
|
210
|
+
elif self.algorithm == SignatureAlgorithm.HMAC_SHA256:
|
|
211
|
+
return hmac.new(private_key, data, hashlib.sha256).digest()
|
|
212
|
+
|
|
213
|
+
elif self.algorithm == SignatureAlgorithm.HMAC_SHA512:
|
|
214
|
+
return hmac.new(private_key, data, hashlib.sha512).digest()
|
|
215
|
+
|
|
216
|
+
elif self.algorithm == SignatureAlgorithm.RSA_SHA256:
|
|
217
|
+
return self._sign_rsa(data, private_key)
|
|
218
|
+
|
|
219
|
+
elif self.algorithm == SignatureAlgorithm.ED25519:
|
|
220
|
+
return self._sign_ed25519(data, private_key)
|
|
221
|
+
|
|
222
|
+
else:
|
|
223
|
+
raise SignatureError(f"Unsupported algorithm: {self.algorithm}")
|
|
224
|
+
|
|
225
|
+
def _sign_rsa(self, data: bytes, private_key: bytes) -> bytes:
|
|
226
|
+
"""Sign data using RSA."""
|
|
227
|
+
try:
|
|
228
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
229
|
+
from cryptography.hazmat.primitives.asymmetric import padding
|
|
230
|
+
|
|
231
|
+
key = serialization.load_pem_private_key(private_key, password=None)
|
|
232
|
+
signature = key.sign(
|
|
233
|
+
data,
|
|
234
|
+
padding.PKCS1v15(),
|
|
235
|
+
hashes.SHA256(),
|
|
236
|
+
)
|
|
237
|
+
return signature
|
|
238
|
+
except ImportError:
|
|
239
|
+
raise SignatureError(
|
|
240
|
+
"RSA signing requires cryptography package. "
|
|
241
|
+
"Install with: pip install cryptography"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def _sign_ed25519(self, data: bytes, private_key: bytes) -> bytes:
|
|
245
|
+
"""Sign data using Ed25519."""
|
|
246
|
+
try:
|
|
247
|
+
from cryptography.hazmat.primitives import serialization
|
|
248
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
249
|
+
|
|
250
|
+
key = serialization.load_pem_private_key(private_key, password=None)
|
|
251
|
+
if not isinstance(key, Ed25519PrivateKey):
|
|
252
|
+
raise SignatureError("Key is not an Ed25519 private key")
|
|
253
|
+
return key.sign(data)
|
|
254
|
+
except ImportError:
|
|
255
|
+
raise SignatureError(
|
|
256
|
+
"Ed25519 signing requires cryptography package. "
|
|
257
|
+
"Install with: pip install cryptography"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def verify_signature(
|
|
261
|
+
self,
|
|
262
|
+
data: bytes,
|
|
263
|
+
signature: bytes,
|
|
264
|
+
public_key: bytes | None = None,
|
|
265
|
+
secret: bytes | None = None,
|
|
266
|
+
) -> bool:
|
|
267
|
+
"""Verify a signature against data.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
data: Data that was signed
|
|
271
|
+
signature: Signature to verify
|
|
272
|
+
public_key: Public key for asymmetric algorithms
|
|
273
|
+
secret: Secret for HMAC algorithms
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
True if signature is valid
|
|
277
|
+
"""
|
|
278
|
+
if self.algorithm in (SignatureAlgorithm.SHA256, SignatureAlgorithm.SHA512):
|
|
279
|
+
# Hash-based (not real signatures, just for integrity)
|
|
280
|
+
expected = self._create_signature(data, b"")
|
|
281
|
+
return hmac.compare_digest(expected, signature)
|
|
282
|
+
|
|
283
|
+
elif self.algorithm in (SignatureAlgorithm.HMAC_SHA256, SignatureAlgorithm.HMAC_SHA512):
|
|
284
|
+
if not secret:
|
|
285
|
+
return False
|
|
286
|
+
expected = self._create_signature(data, secret)
|
|
287
|
+
return hmac.compare_digest(expected, signature)
|
|
288
|
+
|
|
289
|
+
elif self.algorithm == SignatureAlgorithm.RSA_SHA256:
|
|
290
|
+
if not public_key:
|
|
291
|
+
return False
|
|
292
|
+
return self._verify_rsa(data, signature, public_key)
|
|
293
|
+
|
|
294
|
+
elif self.algorithm == SignatureAlgorithm.ED25519:
|
|
295
|
+
if not public_key:
|
|
296
|
+
return False
|
|
297
|
+
return self._verify_ed25519(data, signature, public_key)
|
|
298
|
+
|
|
299
|
+
return False
|
|
300
|
+
|
|
301
|
+
def _verify_rsa(self, data: bytes, signature: bytes, public_key: bytes) -> bool:
|
|
302
|
+
"""Verify RSA signature."""
|
|
303
|
+
try:
|
|
304
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
305
|
+
from cryptography.hazmat.primitives.asymmetric import padding
|
|
306
|
+
from cryptography.exceptions import InvalidSignature
|
|
307
|
+
|
|
308
|
+
key = serialization.load_pem_public_key(public_key)
|
|
309
|
+
try:
|
|
310
|
+
key.verify(signature, data, padding.PKCS1v15(), hashes.SHA256())
|
|
311
|
+
return True
|
|
312
|
+
except InvalidSignature:
|
|
313
|
+
return False
|
|
314
|
+
except ImportError:
|
|
315
|
+
return False
|
|
316
|
+
|
|
317
|
+
def _verify_ed25519(self, data: bytes, signature: bytes, public_key: bytes) -> bool:
|
|
318
|
+
"""Verify Ed25519 signature."""
|
|
319
|
+
try:
|
|
320
|
+
from cryptography.hazmat.primitives import serialization
|
|
321
|
+
from cryptography.exceptions import InvalidSignature
|
|
322
|
+
|
|
323
|
+
key = serialization.load_pem_public_key(public_key)
|
|
324
|
+
try:
|
|
325
|
+
key.verify(signature, data)
|
|
326
|
+
return True
|
|
327
|
+
except InvalidSignature:
|
|
328
|
+
return False
|
|
329
|
+
except ImportError:
|
|
330
|
+
return False
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""Trust store for managing trusted certificates and signers.
|
|
2
|
+
|
|
3
|
+
This module provides a TrustStore implementation for managing
|
|
4
|
+
which signers and certificates are trusted.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime, timezone
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from truthound.plugins.security.protocols import TrustLevel
|
|
18
|
+
from truthound.plugins.security.exceptions import (
|
|
19
|
+
CertificateError,
|
|
20
|
+
CertificateNotFoundError,
|
|
21
|
+
CertificateRevokedError,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class CertificateEntry:
|
|
29
|
+
"""Entry in the trust store."""
|
|
30
|
+
|
|
31
|
+
cert_id: str
|
|
32
|
+
certificate: bytes
|
|
33
|
+
trust_level: TrustLevel
|
|
34
|
+
added_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
35
|
+
expires_at: datetime | None = None
|
|
36
|
+
revoked_at: datetime | None = None
|
|
37
|
+
revocation_reason: str = ""
|
|
38
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def is_revoked(self) -> bool:
|
|
42
|
+
"""Check if certificate is revoked."""
|
|
43
|
+
return self.revoked_at is not None
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def is_expired(self) -> bool:
|
|
47
|
+
"""Check if certificate is expired."""
|
|
48
|
+
if self.expires_at is None:
|
|
49
|
+
return False
|
|
50
|
+
return datetime.now(timezone.utc) > self.expires_at
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def is_valid(self) -> bool:
|
|
54
|
+
"""Check if certificate is valid (not revoked and not expired)."""
|
|
55
|
+
return not self.is_revoked and not self.is_expired
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> dict[str, Any]:
|
|
58
|
+
"""Convert to dictionary."""
|
|
59
|
+
return {
|
|
60
|
+
"cert_id": self.cert_id,
|
|
61
|
+
"trust_level": self.trust_level.value,
|
|
62
|
+
"added_at": self.added_at.isoformat(),
|
|
63
|
+
"expires_at": self.expires_at.isoformat() if self.expires_at else None,
|
|
64
|
+
"revoked_at": self.revoked_at.isoformat() if self.revoked_at else None,
|
|
65
|
+
"revocation_reason": self.revocation_reason,
|
|
66
|
+
"metadata": self.metadata,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TrustStoreImpl:
|
|
71
|
+
"""Implementation of trust store for certificates and signers.
|
|
72
|
+
|
|
73
|
+
Manages a collection of trusted certificates with different
|
|
74
|
+
trust levels. Supports persistence to JSON file.
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
>>> store = TrustStoreImpl()
|
|
78
|
+
>>> cert_id = store.add_trusted_certificate(cert_bytes)
|
|
79
|
+
>>> is_trusted, level = store.is_trusted(cert_bytes)
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
store_path: Path | None = None,
|
|
85
|
+
auto_save: bool = True,
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Initialize trust store.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
store_path: Path to persist store (optional)
|
|
91
|
+
auto_save: Whether to auto-save after modifications
|
|
92
|
+
"""
|
|
93
|
+
self._store_path = store_path
|
|
94
|
+
self._auto_save = auto_save
|
|
95
|
+
self._certificates: dict[str, CertificateEntry] = {}
|
|
96
|
+
self._signer_trust: dict[str, TrustLevel] = {}
|
|
97
|
+
|
|
98
|
+
# Load from file if exists
|
|
99
|
+
if store_path and store_path.exists():
|
|
100
|
+
self._load()
|
|
101
|
+
|
|
102
|
+
def add_trusted_certificate(
|
|
103
|
+
self,
|
|
104
|
+
certificate: bytes,
|
|
105
|
+
trust_level: TrustLevel = TrustLevel.TRUSTED,
|
|
106
|
+
metadata: dict[str, Any] | None = None,
|
|
107
|
+
) -> str:
|
|
108
|
+
"""Add a certificate to the trust store.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
certificate: Certificate bytes (DER or PEM)
|
|
112
|
+
trust_level: Trust level to assign
|
|
113
|
+
metadata: Additional metadata
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Certificate ID
|
|
117
|
+
"""
|
|
118
|
+
# Generate certificate ID from hash
|
|
119
|
+
cert_id = self._compute_cert_id(certificate)
|
|
120
|
+
|
|
121
|
+
# Check if already exists
|
|
122
|
+
if cert_id in self._certificates:
|
|
123
|
+
existing = self._certificates[cert_id]
|
|
124
|
+
if existing.is_revoked:
|
|
125
|
+
raise CertificateRevokedError(
|
|
126
|
+
f"Certificate {cert_id} was revoked",
|
|
127
|
+
cert_id=cert_id,
|
|
128
|
+
revoked_at=existing.revoked_at,
|
|
129
|
+
reason=existing.revocation_reason,
|
|
130
|
+
)
|
|
131
|
+
# Update trust level if higher
|
|
132
|
+
if trust_level == TrustLevel.TRUSTED:
|
|
133
|
+
existing = CertificateEntry(
|
|
134
|
+
cert_id=existing.cert_id,
|
|
135
|
+
certificate=existing.certificate,
|
|
136
|
+
trust_level=trust_level,
|
|
137
|
+
added_at=existing.added_at,
|
|
138
|
+
expires_at=existing.expires_at,
|
|
139
|
+
metadata={**existing.metadata, **(metadata or {})},
|
|
140
|
+
)
|
|
141
|
+
self._certificates[cert_id] = existing
|
|
142
|
+
return cert_id
|
|
143
|
+
|
|
144
|
+
# Create new entry
|
|
145
|
+
entry = CertificateEntry(
|
|
146
|
+
cert_id=cert_id,
|
|
147
|
+
certificate=certificate,
|
|
148
|
+
trust_level=trust_level,
|
|
149
|
+
metadata=metadata or {},
|
|
150
|
+
)
|
|
151
|
+
self._certificates[cert_id] = entry
|
|
152
|
+
|
|
153
|
+
# Extract signer ID from certificate if possible
|
|
154
|
+
signer_id = self._extract_signer_id(certificate)
|
|
155
|
+
if signer_id:
|
|
156
|
+
self._signer_trust[signer_id] = trust_level
|
|
157
|
+
|
|
158
|
+
logger.info(f"Added certificate {cert_id} with trust level {trust_level.value}")
|
|
159
|
+
|
|
160
|
+
if self._auto_save:
|
|
161
|
+
self._save()
|
|
162
|
+
|
|
163
|
+
return cert_id
|
|
164
|
+
|
|
165
|
+
def remove_certificate(self, cert_id: str) -> bool:
|
|
166
|
+
"""Remove a certificate from trust store.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
cert_id: Certificate ID
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
True if removed, False if not found
|
|
173
|
+
"""
|
|
174
|
+
if cert_id not in self._certificates:
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
entry = self._certificates.pop(cert_id)
|
|
178
|
+
|
|
179
|
+
# Also remove signer trust if applicable
|
|
180
|
+
signer_id = entry.metadata.get("signer_id")
|
|
181
|
+
if signer_id:
|
|
182
|
+
self._signer_trust.pop(signer_id, None)
|
|
183
|
+
|
|
184
|
+
logger.info(f"Removed certificate {cert_id}")
|
|
185
|
+
|
|
186
|
+
if self._auto_save:
|
|
187
|
+
self._save()
|
|
188
|
+
|
|
189
|
+
return True
|
|
190
|
+
|
|
191
|
+
def revoke_certificate(
|
|
192
|
+
self,
|
|
193
|
+
cert_id: str,
|
|
194
|
+
reason: str = "",
|
|
195
|
+
) -> bool:
|
|
196
|
+
"""Revoke a certificate.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
cert_id: Certificate ID
|
|
200
|
+
reason: Revocation reason
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
True if revoked, False if not found
|
|
204
|
+
"""
|
|
205
|
+
if cert_id not in self._certificates:
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
entry = self._certificates[cert_id]
|
|
209
|
+
self._certificates[cert_id] = CertificateEntry(
|
|
210
|
+
cert_id=entry.cert_id,
|
|
211
|
+
certificate=entry.certificate,
|
|
212
|
+
trust_level=TrustLevel.REVOKED,
|
|
213
|
+
added_at=entry.added_at,
|
|
214
|
+
expires_at=entry.expires_at,
|
|
215
|
+
revoked_at=datetime.now(timezone.utc),
|
|
216
|
+
revocation_reason=reason,
|
|
217
|
+
metadata=entry.metadata,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Also revoke signer trust
|
|
221
|
+
signer_id = entry.metadata.get("signer_id")
|
|
222
|
+
if signer_id:
|
|
223
|
+
self._signer_trust[signer_id] = TrustLevel.REVOKED
|
|
224
|
+
|
|
225
|
+
logger.info(f"Revoked certificate {cert_id}: {reason}")
|
|
226
|
+
|
|
227
|
+
if self._auto_save:
|
|
228
|
+
self._save()
|
|
229
|
+
|
|
230
|
+
return True
|
|
231
|
+
|
|
232
|
+
def is_trusted(self, certificate: bytes) -> tuple[bool, TrustLevel]:
|
|
233
|
+
"""Check if certificate is trusted.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
certificate: Certificate to check
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Tuple of (is_trusted, trust_level)
|
|
240
|
+
"""
|
|
241
|
+
cert_id = self._compute_cert_id(certificate)
|
|
242
|
+
|
|
243
|
+
if cert_id not in self._certificates:
|
|
244
|
+
return False, TrustLevel.UNKNOWN
|
|
245
|
+
|
|
246
|
+
entry = self._certificates[cert_id]
|
|
247
|
+
|
|
248
|
+
if entry.is_revoked:
|
|
249
|
+
return False, TrustLevel.REVOKED
|
|
250
|
+
|
|
251
|
+
if entry.is_expired:
|
|
252
|
+
return False, TrustLevel.UNKNOWN
|
|
253
|
+
|
|
254
|
+
is_trusted = entry.trust_level in (TrustLevel.TRUSTED, TrustLevel.VERIFIED)
|
|
255
|
+
return is_trusted, entry.trust_level
|
|
256
|
+
|
|
257
|
+
def get_trust_level(self, signer_id: str) -> TrustLevel:
|
|
258
|
+
"""Get trust level for a signer.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
signer_id: Signer identifier
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Trust level of the signer
|
|
265
|
+
"""
|
|
266
|
+
return self._signer_trust.get(signer_id, TrustLevel.UNKNOWN)
|
|
267
|
+
|
|
268
|
+
def set_signer_trust(
|
|
269
|
+
self,
|
|
270
|
+
signer_id: str,
|
|
271
|
+
trust_level: TrustLevel,
|
|
272
|
+
) -> None:
|
|
273
|
+
"""Set trust level for a signer.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
signer_id: Signer identifier
|
|
277
|
+
trust_level: Trust level to set
|
|
278
|
+
"""
|
|
279
|
+
self._signer_trust[signer_id] = trust_level
|
|
280
|
+
|
|
281
|
+
if self._auto_save:
|
|
282
|
+
self._save()
|
|
283
|
+
|
|
284
|
+
def list_certificates(self) -> list[dict[str, Any]]:
|
|
285
|
+
"""List all certificates in store.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
List of certificate info dicts
|
|
289
|
+
"""
|
|
290
|
+
return [entry.to_dict() for entry in self._certificates.values()]
|
|
291
|
+
|
|
292
|
+
def list_trusted_signers(self) -> list[str]:
|
|
293
|
+
"""List all trusted signer IDs.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
List of trusted signer IDs
|
|
297
|
+
"""
|
|
298
|
+
return [
|
|
299
|
+
signer_id
|
|
300
|
+
for signer_id, level in self._signer_trust.items()
|
|
301
|
+
if level == TrustLevel.TRUSTED
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
def _compute_cert_id(self, certificate: bytes) -> str:
|
|
305
|
+
"""Compute certificate ID from content hash."""
|
|
306
|
+
return hashlib.sha256(certificate).hexdigest()[:16]
|
|
307
|
+
|
|
308
|
+
def _extract_signer_id(self, certificate: bytes) -> str | None:
|
|
309
|
+
"""Try to extract signer ID from certificate."""
|
|
310
|
+
try:
|
|
311
|
+
from cryptography import x509
|
|
312
|
+
|
|
313
|
+
cert = x509.load_pem_x509_certificate(certificate)
|
|
314
|
+
# Use subject common name as signer ID
|
|
315
|
+
for attr in cert.subject:
|
|
316
|
+
if attr.oid == x509.oid.NameOID.COMMON_NAME:
|
|
317
|
+
return attr.value
|
|
318
|
+
except Exception:
|
|
319
|
+
pass
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
def _save(self) -> None:
|
|
323
|
+
"""Save trust store to file."""
|
|
324
|
+
if not self._store_path:
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
data = {
|
|
328
|
+
"certificates": {
|
|
329
|
+
cert_id: entry.to_dict()
|
|
330
|
+
for cert_id, entry in self._certificates.items()
|
|
331
|
+
},
|
|
332
|
+
"signer_trust": {
|
|
333
|
+
signer_id: level.value
|
|
334
|
+
for signer_id, level in self._signer_trust.items()
|
|
335
|
+
},
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
self._store_path.parent.mkdir(parents=True, exist_ok=True)
|
|
339
|
+
with open(self._store_path, "w") as f:
|
|
340
|
+
json.dump(data, f, indent=2)
|
|
341
|
+
|
|
342
|
+
def _load(self) -> None:
|
|
343
|
+
"""Load trust store from file."""
|
|
344
|
+
if not self._store_path or not self._store_path.exists():
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
try:
|
|
348
|
+
with open(self._store_path, "r") as f:
|
|
349
|
+
data = json.load(f)
|
|
350
|
+
|
|
351
|
+
# Load signer trust
|
|
352
|
+
for signer_id, level_str in data.get("signer_trust", {}).items():
|
|
353
|
+
self._signer_trust[signer_id] = TrustLevel(level_str)
|
|
354
|
+
|
|
355
|
+
# Note: Certificate bytes are not stored in JSON, only metadata
|
|
356
|
+
# Certificates must be re-added after loading
|
|
357
|
+
|
|
358
|
+
logger.info(f"Loaded trust store from {self._store_path}")
|
|
359
|
+
except Exception as e:
|
|
360
|
+
logger.error(f"Failed to load trust store: {e}")
|
|
361
|
+
|
|
362
|
+
def clear(self) -> None:
|
|
363
|
+
"""Clear all certificates and trust levels."""
|
|
364
|
+
self._certificates.clear()
|
|
365
|
+
self._signer_trust.clear()
|
|
366
|
+
|
|
367
|
+
if self._auto_save and self._store_path:
|
|
368
|
+
self._save()
|