fraiseql-confiture 0.3.4__cp311-cp311-win_amd64.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.
- confiture/__init__.py +48 -0
- confiture/_core.cp311-win_amd64.pyd +0 -0
- confiture/cli/__init__.py +0 -0
- confiture/cli/dry_run.py +116 -0
- confiture/cli/lint_formatter.py +193 -0
- confiture/cli/main.py +1656 -0
- confiture/config/__init__.py +0 -0
- confiture/config/environment.py +263 -0
- confiture/core/__init__.py +51 -0
- confiture/core/anonymization/__init__.py +0 -0
- confiture/core/anonymization/audit.py +485 -0
- confiture/core/anonymization/benchmarking.py +372 -0
- confiture/core/anonymization/breach_notification.py +652 -0
- confiture/core/anonymization/compliance.py +617 -0
- confiture/core/anonymization/composer.py +298 -0
- confiture/core/anonymization/data_subject_rights.py +669 -0
- confiture/core/anonymization/factory.py +319 -0
- confiture/core/anonymization/governance.py +737 -0
- confiture/core/anonymization/performance.py +1092 -0
- confiture/core/anonymization/profile.py +284 -0
- confiture/core/anonymization/registry.py +195 -0
- confiture/core/anonymization/security/kms_manager.py +547 -0
- confiture/core/anonymization/security/lineage.py +888 -0
- confiture/core/anonymization/security/token_store.py +686 -0
- confiture/core/anonymization/strategies/__init__.py +41 -0
- confiture/core/anonymization/strategies/address.py +359 -0
- confiture/core/anonymization/strategies/credit_card.py +374 -0
- confiture/core/anonymization/strategies/custom.py +161 -0
- confiture/core/anonymization/strategies/date.py +218 -0
- confiture/core/anonymization/strategies/differential_privacy.py +398 -0
- confiture/core/anonymization/strategies/email.py +141 -0
- confiture/core/anonymization/strategies/format_preserving_encryption.py +310 -0
- confiture/core/anonymization/strategies/hash.py +150 -0
- confiture/core/anonymization/strategies/ip_address.py +235 -0
- confiture/core/anonymization/strategies/masking_retention.py +252 -0
- confiture/core/anonymization/strategies/name.py +298 -0
- confiture/core/anonymization/strategies/phone.py +119 -0
- confiture/core/anonymization/strategies/preserve.py +85 -0
- confiture/core/anonymization/strategies/redact.py +101 -0
- confiture/core/anonymization/strategies/salted_hashing.py +322 -0
- confiture/core/anonymization/strategies/text_redaction.py +183 -0
- confiture/core/anonymization/strategies/tokenization.py +334 -0
- confiture/core/anonymization/strategy.py +241 -0
- confiture/core/anonymization/syncer_audit.py +357 -0
- confiture/core/blue_green.py +683 -0
- confiture/core/builder.py +500 -0
- confiture/core/checksum.py +358 -0
- confiture/core/connection.py +132 -0
- confiture/core/differ.py +522 -0
- confiture/core/drift.py +564 -0
- confiture/core/dry_run.py +182 -0
- confiture/core/health.py +313 -0
- confiture/core/hooks/__init__.py +87 -0
- confiture/core/hooks/base.py +232 -0
- confiture/core/hooks/context.py +146 -0
- confiture/core/hooks/execution_strategies.py +57 -0
- confiture/core/hooks/observability.py +220 -0
- confiture/core/hooks/phases.py +53 -0
- confiture/core/hooks/registry.py +295 -0
- confiture/core/large_tables.py +775 -0
- confiture/core/linting/__init__.py +70 -0
- confiture/core/linting/composer.py +192 -0
- confiture/core/linting/libraries/__init__.py +17 -0
- confiture/core/linting/libraries/gdpr.py +168 -0
- confiture/core/linting/libraries/general.py +184 -0
- confiture/core/linting/libraries/hipaa.py +144 -0
- confiture/core/linting/libraries/pci_dss.py +104 -0
- confiture/core/linting/libraries/sox.py +120 -0
- confiture/core/linting/schema_linter.py +491 -0
- confiture/core/linting/versioning.py +151 -0
- confiture/core/locking.py +389 -0
- confiture/core/migration_generator.py +298 -0
- confiture/core/migrator.py +793 -0
- confiture/core/observability/__init__.py +44 -0
- confiture/core/observability/audit.py +323 -0
- confiture/core/observability/logging.py +187 -0
- confiture/core/observability/metrics.py +174 -0
- confiture/core/observability/tracing.py +192 -0
- confiture/core/pg_version.py +418 -0
- confiture/core/pool.py +406 -0
- confiture/core/risk/__init__.py +39 -0
- confiture/core/risk/predictor.py +188 -0
- confiture/core/risk/scoring.py +248 -0
- confiture/core/rollback_generator.py +388 -0
- confiture/core/schema_analyzer.py +769 -0
- confiture/core/schema_to_schema.py +590 -0
- confiture/core/security/__init__.py +32 -0
- confiture/core/security/logging.py +201 -0
- confiture/core/security/validation.py +416 -0
- confiture/core/signals.py +371 -0
- confiture/core/syncer.py +540 -0
- confiture/exceptions.py +192 -0
- confiture/integrations/__init__.py +0 -0
- confiture/models/__init__.py +0 -0
- confiture/models/lint.py +193 -0
- confiture/models/migration.py +180 -0
- confiture/models/schema.py +203 -0
- confiture/scenarios/__init__.py +36 -0
- confiture/scenarios/compliance.py +586 -0
- confiture/scenarios/ecommerce.py +199 -0
- confiture/scenarios/financial.py +253 -0
- confiture/scenarios/healthcare.py +315 -0
- confiture/scenarios/multi_tenant.py +340 -0
- confiture/scenarios/saas.py +295 -0
- confiture/testing/FRAMEWORK_API.md +722 -0
- confiture/testing/__init__.py +38 -0
- confiture/testing/fixtures/__init__.py +11 -0
- confiture/testing/fixtures/data_validator.py +229 -0
- confiture/testing/fixtures/migration_runner.py +167 -0
- confiture/testing/fixtures/schema_snapshotter.py +352 -0
- confiture/testing/frameworks/__init__.py +10 -0
- confiture/testing/frameworks/mutation.py +587 -0
- confiture/testing/frameworks/performance.py +479 -0
- confiture/testing/utils/__init__.py +0 -0
- fraiseql_confiture-0.3.4.dist-info/METADATA +438 -0
- fraiseql_confiture-0.3.4.dist-info/RECORD +119 -0
- fraiseql_confiture-0.3.4.dist-info/WHEEL +4 -0
- fraiseql_confiture-0.3.4.dist-info/entry_points.txt +2 -0
- fraiseql_confiture-0.3.4.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
|