fraiseql-confiture 0.3.7__cp311-cp311-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. confiture/__init__.py +48 -0
  2. confiture/_core.cpython-311-darwin.so +0 -0
  3. confiture/cli/__init__.py +0 -0
  4. confiture/cli/dry_run.py +116 -0
  5. confiture/cli/lint_formatter.py +193 -0
  6. confiture/cli/main.py +1893 -0
  7. confiture/config/__init__.py +0 -0
  8. confiture/config/environment.py +263 -0
  9. confiture/core/__init__.py +51 -0
  10. confiture/core/anonymization/__init__.py +0 -0
  11. confiture/core/anonymization/audit.py +485 -0
  12. confiture/core/anonymization/benchmarking.py +372 -0
  13. confiture/core/anonymization/breach_notification.py +652 -0
  14. confiture/core/anonymization/compliance.py +617 -0
  15. confiture/core/anonymization/composer.py +298 -0
  16. confiture/core/anonymization/data_subject_rights.py +669 -0
  17. confiture/core/anonymization/factory.py +319 -0
  18. confiture/core/anonymization/governance.py +737 -0
  19. confiture/core/anonymization/performance.py +1092 -0
  20. confiture/core/anonymization/profile.py +284 -0
  21. confiture/core/anonymization/registry.py +195 -0
  22. confiture/core/anonymization/security/kms_manager.py +547 -0
  23. confiture/core/anonymization/security/lineage.py +888 -0
  24. confiture/core/anonymization/security/token_store.py +686 -0
  25. confiture/core/anonymization/strategies/__init__.py +41 -0
  26. confiture/core/anonymization/strategies/address.py +359 -0
  27. confiture/core/anonymization/strategies/credit_card.py +374 -0
  28. confiture/core/anonymization/strategies/custom.py +161 -0
  29. confiture/core/anonymization/strategies/date.py +218 -0
  30. confiture/core/anonymization/strategies/differential_privacy.py +398 -0
  31. confiture/core/anonymization/strategies/email.py +141 -0
  32. confiture/core/anonymization/strategies/format_preserving_encryption.py +310 -0
  33. confiture/core/anonymization/strategies/hash.py +150 -0
  34. confiture/core/anonymization/strategies/ip_address.py +235 -0
  35. confiture/core/anonymization/strategies/masking_retention.py +252 -0
  36. confiture/core/anonymization/strategies/name.py +298 -0
  37. confiture/core/anonymization/strategies/phone.py +119 -0
  38. confiture/core/anonymization/strategies/preserve.py +85 -0
  39. confiture/core/anonymization/strategies/redact.py +101 -0
  40. confiture/core/anonymization/strategies/salted_hashing.py +322 -0
  41. confiture/core/anonymization/strategies/text_redaction.py +183 -0
  42. confiture/core/anonymization/strategies/tokenization.py +334 -0
  43. confiture/core/anonymization/strategy.py +241 -0
  44. confiture/core/anonymization/syncer_audit.py +357 -0
  45. confiture/core/blue_green.py +683 -0
  46. confiture/core/builder.py +500 -0
  47. confiture/core/checksum.py +358 -0
  48. confiture/core/connection.py +184 -0
  49. confiture/core/differ.py +522 -0
  50. confiture/core/drift.py +564 -0
  51. confiture/core/dry_run.py +182 -0
  52. confiture/core/health.py +313 -0
  53. confiture/core/hooks/__init__.py +87 -0
  54. confiture/core/hooks/base.py +232 -0
  55. confiture/core/hooks/context.py +146 -0
  56. confiture/core/hooks/execution_strategies.py +57 -0
  57. confiture/core/hooks/observability.py +220 -0
  58. confiture/core/hooks/phases.py +53 -0
  59. confiture/core/hooks/registry.py +295 -0
  60. confiture/core/large_tables.py +775 -0
  61. confiture/core/linting/__init__.py +70 -0
  62. confiture/core/linting/composer.py +192 -0
  63. confiture/core/linting/libraries/__init__.py +17 -0
  64. confiture/core/linting/libraries/gdpr.py +168 -0
  65. confiture/core/linting/libraries/general.py +184 -0
  66. confiture/core/linting/libraries/hipaa.py +144 -0
  67. confiture/core/linting/libraries/pci_dss.py +104 -0
  68. confiture/core/linting/libraries/sox.py +120 -0
  69. confiture/core/linting/schema_linter.py +491 -0
  70. confiture/core/linting/versioning.py +151 -0
  71. confiture/core/locking.py +389 -0
  72. confiture/core/migration_generator.py +298 -0
  73. confiture/core/migrator.py +882 -0
  74. confiture/core/observability/__init__.py +44 -0
  75. confiture/core/observability/audit.py +323 -0
  76. confiture/core/observability/logging.py +187 -0
  77. confiture/core/observability/metrics.py +174 -0
  78. confiture/core/observability/tracing.py +192 -0
  79. confiture/core/pg_version.py +418 -0
  80. confiture/core/pool.py +406 -0
  81. confiture/core/risk/__init__.py +39 -0
  82. confiture/core/risk/predictor.py +188 -0
  83. confiture/core/risk/scoring.py +248 -0
  84. confiture/core/rollback_generator.py +388 -0
  85. confiture/core/schema_analyzer.py +769 -0
  86. confiture/core/schema_to_schema.py +590 -0
  87. confiture/core/security/__init__.py +32 -0
  88. confiture/core/security/logging.py +201 -0
  89. confiture/core/security/validation.py +416 -0
  90. confiture/core/signals.py +371 -0
  91. confiture/core/syncer.py +540 -0
  92. confiture/exceptions.py +192 -0
  93. confiture/integrations/__init__.py +0 -0
  94. confiture/models/__init__.py +24 -0
  95. confiture/models/lint.py +193 -0
  96. confiture/models/migration.py +265 -0
  97. confiture/models/schema.py +203 -0
  98. confiture/models/sql_file_migration.py +225 -0
  99. confiture/scenarios/__init__.py +36 -0
  100. confiture/scenarios/compliance.py +586 -0
  101. confiture/scenarios/ecommerce.py +199 -0
  102. confiture/scenarios/financial.py +253 -0
  103. confiture/scenarios/healthcare.py +315 -0
  104. confiture/scenarios/multi_tenant.py +340 -0
  105. confiture/scenarios/saas.py +295 -0
  106. confiture/testing/FRAMEWORK_API.md +722 -0
  107. confiture/testing/__init__.py +100 -0
  108. confiture/testing/fixtures/__init__.py +11 -0
  109. confiture/testing/fixtures/data_validator.py +229 -0
  110. confiture/testing/fixtures/migration_runner.py +167 -0
  111. confiture/testing/fixtures/schema_snapshotter.py +352 -0
  112. confiture/testing/frameworks/__init__.py +10 -0
  113. confiture/testing/frameworks/mutation.py +587 -0
  114. confiture/testing/frameworks/performance.py +479 -0
  115. confiture/testing/loader.py +225 -0
  116. confiture/testing/pytest/__init__.py +38 -0
  117. confiture/testing/pytest_plugin.py +190 -0
  118. confiture/testing/sandbox.py +304 -0
  119. confiture/testing/utils/__init__.py +0 -0
  120. fraiseql_confiture-0.3.7.dist-info/METADATA +438 -0
  121. fraiseql_confiture-0.3.7.dist-info/RECORD +124 -0
  122. fraiseql_confiture-0.3.7.dist-info/WHEEL +4 -0
  123. fraiseql_confiture-0.3.7.dist-info/entry_points.txt +4 -0
  124. fraiseql_confiture-0.3.7.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,357 @@
1
+ """Integration of audit logging with ProductionSyncer.
2
+
3
+ This module provides helpers to log anonymization operations performed
4
+ during data synchronization with full audit trail for GDPR compliance.
5
+
6
+ Features:
7
+ - Automatic audit entry creation for sync operations
8
+ - Signature verification for tamper detection
9
+ - Profile hashing for integrity checks
10
+ - User and hostname tracking
11
+ - Integration with ProductionSyncer
12
+ """
13
+
14
+ import hashlib
15
+ import json
16
+ from typing import Any
17
+
18
+ import psycopg
19
+
20
+ from confiture.core.anonymization.audit import (
21
+ AuditEntry,
22
+ AuditLogger,
23
+ create_audit_entry,
24
+ verify_audit_entry,
25
+ )
26
+ from confiture.core.anonymization.profile import AnonymizationProfile
27
+
28
+
29
+ def hash_profile(profile: AnonymizationProfile | None) -> str:
30
+ """Create SHA256 hash of anonymization profile for integrity check.
31
+
32
+ Args:
33
+ profile: AnonymizationProfile to hash, or None
34
+
35
+ Returns:
36
+ SHA256 hash of profile JSON representation
37
+
38
+ Example:
39
+ >>> profile = AnonymizationProfile(...)
40
+ >>> hash_val = hash_profile(profile)
41
+ >>> print(len(hash_val))
42
+ 64 # SHA256 hex is 64 chars
43
+ """
44
+ if profile is None:
45
+ # No anonymization, use empty hash
46
+ return hashlib.sha256(b"").hexdigest()
47
+
48
+ # Create deterministic JSON of profile
49
+ profile_dict = {
50
+ "name": profile.name,
51
+ "version": profile.version,
52
+ "global_seed": profile.global_seed,
53
+ "strategies": {
54
+ name: {
55
+ "type": strategy.type,
56
+ "config": strategy.config,
57
+ }
58
+ for name, strategy in profile.strategies.items()
59
+ },
60
+ "tables": {
61
+ table_name: {
62
+ "rules": [
63
+ {
64
+ "column": rule.column,
65
+ "strategy": rule.strategy,
66
+ "seed": rule.seed,
67
+ }
68
+ for rule in table_def.rules
69
+ ]
70
+ }
71
+ for table_name, table_def in profile.tables.items()
72
+ },
73
+ }
74
+
75
+ profile_json = json.dumps(profile_dict, sort_keys=True)
76
+ return hashlib.sha256(profile_json.encode()).hexdigest()
77
+
78
+
79
+ def create_sync_audit_entry(
80
+ user: str,
81
+ source_database: str,
82
+ target_database: str,
83
+ profile: AnonymizationProfile | None,
84
+ tables_synced: list[str],
85
+ rows_by_table: dict[str, int],
86
+ strategies_applied: dict[str, int],
87
+ verification_passed: bool = True,
88
+ verification_report: dict[str, Any] | None = None,
89
+ secret: str | None = None,
90
+ ) -> AuditEntry:
91
+ """Create audit entry for data synchronization operation.
92
+
93
+ Convenience function that creates a complete, signed audit entry
94
+ for a sync operation.
95
+
96
+ Args:
97
+ user: User who performed the sync (email or system account)
98
+ source_database: Source database identifier
99
+ target_database: Target database identifier
100
+ profile: Anonymization profile used (None if no anonymization)
101
+ tables_synced: List of tables synchronized
102
+ rows_by_table: Dict of table → row count
103
+ strategies_applied: Dict of strategy → application count
104
+ verification_passed: Whether verification checks passed
105
+ verification_report: Detailed verification results
106
+ secret: Secret key for HMAC (or AUDIT_LOG_SECRET env var)
107
+
108
+ Returns:
109
+ Signed AuditEntry ready for logging
110
+
111
+ Example:
112
+ >>> entry = create_sync_audit_entry(
113
+ ... user="admin@example.com",
114
+ ... source_database="prod_main",
115
+ ... target_database="staging_copy",
116
+ ... profile=profile,
117
+ ... tables_synced=["users", "orders"],
118
+ ... rows_by_table={"users": 1000, "orders": 5000},
119
+ ... strategies_applied={"email": 1000, "hash": 5000},
120
+ ... )
121
+ >>> logger.log_sync(entry)
122
+ """
123
+ profile_hash = hash_profile(profile)
124
+ profile_name = profile.name if profile else "none"
125
+ profile_version = profile.version if profile else "0.0"
126
+
127
+ verification_json = (
128
+ json.dumps(verification_report, sort_keys=True) if verification_report else "{}"
129
+ )
130
+
131
+ return create_audit_entry(
132
+ user=user,
133
+ source_db=source_database,
134
+ target_db=target_database,
135
+ profile_name=profile_name,
136
+ profile_version=profile_version,
137
+ profile_hash=profile_hash,
138
+ tables=tables_synced,
139
+ rows_by_table=rows_by_table,
140
+ strategies_by_type=strategies_applied,
141
+ verification_passed=verification_passed,
142
+ verification_report=verification_json,
143
+ secret=secret,
144
+ )
145
+
146
+
147
+ class AuditedProductionSyncer:
148
+ """Wrapper for ProductionSyncer that logs operations to audit trail.
149
+
150
+ This class wraps an existing ProductionSyncer and adds audit logging
151
+ functionality, creating signed audit entries for all sync operations.
152
+
153
+ Example:
154
+ >>> from confiture.core.syncer import ProductionSyncer
155
+ >>> syncer = ProductionSyncer("prod", "staging")
156
+ >>> audited = AuditedProductionSyncer(syncer, target_db_connection)
157
+ >>> entry = audited.create_sync_entry(
158
+ ... user="admin@example.com",
159
+ ... profile=my_profile,
160
+ ... tables_synced=["users", "orders"],
161
+ ... rows_by_table={"users": 1000, "orders": 5000},
162
+ ... strategies_applied={"email": 1000}
163
+ ... )
164
+ >>> audited.log_sync_entry(entry)
165
+ """
166
+
167
+ def __init__(self, syncer: Any, target_connection: psycopg.Connection):
168
+ """Initialize audited syncer.
169
+
170
+ Args:
171
+ syncer: ProductionSyncer instance to wrap
172
+ target_connection: PostgreSQL connection for audit logging
173
+ """
174
+ self.syncer = syncer
175
+ self.target_connection = target_connection
176
+ self.audit_logger = AuditLogger(target_connection)
177
+
178
+ def create_sync_entry(
179
+ self,
180
+ user: str,
181
+ profile: AnonymizationProfile | None,
182
+ tables_synced: list[str],
183
+ rows_by_table: dict[str, int],
184
+ strategies_applied: dict[str, int],
185
+ verification_passed: bool = True,
186
+ verification_report: dict[str, Any] | None = None,
187
+ ) -> AuditEntry:
188
+ """Create signed audit entry for sync operation.
189
+
190
+ Args:
191
+ user: User who performed the sync
192
+ profile: Anonymization profile used
193
+ tables_synced: Tables that were synced
194
+ rows_by_table: Row counts per table
195
+ strategies_applied: Strategy application counts
196
+ verification_passed: Whether verification passed
197
+ verification_report: Verification details
198
+
199
+ Returns:
200
+ Signed AuditEntry
201
+ """
202
+ source_db = self.syncer.source_config.database
203
+ target_db = self.syncer.target_config.database
204
+
205
+ return create_sync_audit_entry(
206
+ user=user,
207
+ source_database=f"{source_db}@{self.syncer.source_config.host}",
208
+ target_database=f"{target_db}@{self.syncer.target_config.host}",
209
+ profile=profile,
210
+ tables_synced=tables_synced,
211
+ rows_by_table=rows_by_table,
212
+ strategies_applied=strategies_applied,
213
+ verification_passed=verification_passed,
214
+ verification_report=verification_report,
215
+ )
216
+
217
+ def log_sync_entry(self, entry: AuditEntry) -> None:
218
+ """Log sync operation to audit trail.
219
+
220
+ Args:
221
+ entry: AuditEntry to log
222
+ """
223
+ self.audit_logger.log_sync(entry)
224
+
225
+ def verify_audit_entry(self, entry: AuditEntry, secret: str | None = None) -> bool:
226
+ """Verify integrity of logged audit entry.
227
+
228
+ Args:
229
+ entry: AuditEntry to verify
230
+ secret: Secret key for HMAC (or AUDIT_LOG_SECRET env var)
231
+
232
+ Returns:
233
+ True if signature is valid, False otherwise
234
+ """
235
+ return verify_audit_entry(entry, secret)
236
+
237
+ def get_sync_audit_log(self, limit: int = 100) -> list[AuditEntry]:
238
+ """Get recent sync audit log entries.
239
+
240
+ Args:
241
+ limit: Maximum number of entries to return
242
+
243
+ Returns:
244
+ List of recent AuditEntry instances
245
+ """
246
+ return self.audit_logger.get_audit_log(limit)
247
+
248
+
249
+ def verify_sync_audit_trail(
250
+ target_connection: psycopg.Connection,
251
+ secret: str | None = None,
252
+ strict: bool = False,
253
+ ) -> dict[str, Any]:
254
+ """Verify integrity of all audit log entries.
255
+
256
+ Checks that all audit entries have valid signatures, detecting any
257
+ tampering or unauthorized modifications.
258
+
259
+ Args:
260
+ target_connection: PostgreSQL connection to audit table
261
+ secret: Secret key for HMAC verification
262
+ strict: If True, raise exception on any invalid entry
263
+
264
+ Returns:
265
+ Dictionary with verification results:
266
+ {
267
+ "total_entries": int,
268
+ "valid_entries": int,
269
+ "invalid_entries": int,
270
+ "verification_passed": bool,
271
+ "invalid_ids": list[str],
272
+ }
273
+
274
+ Raises:
275
+ ValueError: If strict=True and any entry is invalid
276
+ """
277
+ logger = AuditLogger(target_connection)
278
+ entries = logger.get_audit_log(limit=10000) # Get all entries
279
+
280
+ total = len(entries)
281
+ valid = 0
282
+ invalid = []
283
+
284
+ for entry in entries:
285
+ if verify_audit_entry(entry, secret):
286
+ valid += 1
287
+ else:
288
+ invalid.append(str(entry.id))
289
+
290
+ if strict and invalid:
291
+ raise ValueError(f"Found {len(invalid)} invalid audit entries: {invalid}")
292
+
293
+ return {
294
+ "total_entries": total,
295
+ "valid_entries": valid,
296
+ "invalid_entries": len(invalid),
297
+ "verification_passed": len(invalid) == 0,
298
+ "invalid_ids": invalid,
299
+ }
300
+
301
+
302
+ def audit_sync_operation(
303
+ syncer: Any,
304
+ target_connection: psycopg.Connection,
305
+ user: str,
306
+ profile: AnonymizationProfile | None,
307
+ tables_synced: list[str],
308
+ rows_by_table: dict[str, int],
309
+ strategies_applied: dict[str, int],
310
+ verification_passed: bool = True,
311
+ ) -> AuditEntry:
312
+ """Helper function to create and log audit entry for sync operation.
313
+
314
+ Convenience function that handles the complete audit flow:
315
+ 1. Creates audit entry
316
+ 2. Signs it with HMAC
317
+ 3. Logs to database
318
+ 4. Returns entry for verification
319
+
320
+ Args:
321
+ syncer: ProductionSyncer instance
322
+ target_connection: PostgreSQL connection
323
+ user: User who performed sync
324
+ profile: Anonymization profile
325
+ tables_synced: Tables synchronized
326
+ rows_by_table: Row counts
327
+ strategies_applied: Strategy counts
328
+ verification_passed: Whether verification passed
329
+
330
+ Returns:
331
+ Logged AuditEntry
332
+
333
+ Example:
334
+ >>> entry = audit_sync_operation(
335
+ ... syncer=syncer,
336
+ ... target_connection=conn,
337
+ ... user="admin@example.com",
338
+ ... profile=profile,
339
+ ... tables_synced=["users"],
340
+ ... rows_by_table={"users": 1000},
341
+ ... strategies_applied={"email": 1000}
342
+ ... )
343
+ >>> print(f"Audit entry logged: {entry.id}")
344
+ """
345
+ audited = AuditedProductionSyncer(syncer, target_connection)
346
+
347
+ entry = audited.create_sync_entry(
348
+ user=user,
349
+ profile=profile,
350
+ tables_synced=tables_synced,
351
+ rows_by_table=rows_by_table,
352
+ strategies_applied=strategies_applied,
353
+ verification_passed=verification_passed,
354
+ )
355
+
356
+ audited.log_sync_entry(entry)
357
+ return entry