kailash 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kailash/__init__.py +1 -1
- kailash/client/__init__.py +12 -0
- kailash/client/enhanced_client.py +306 -0
- kailash/core/actors/__init__.py +16 -0
- kailash/core/actors/connection_actor.py +566 -0
- kailash/core/actors/supervisor.py +364 -0
- kailash/edge/__init__.py +16 -0
- kailash/edge/compliance.py +834 -0
- kailash/edge/discovery.py +659 -0
- kailash/edge/location.py +582 -0
- kailash/gateway/__init__.py +33 -0
- kailash/gateway/api.py +289 -0
- kailash/gateway/enhanced_gateway.py +357 -0
- kailash/gateway/resource_resolver.py +217 -0
- kailash/gateway/security.py +227 -0
- kailash/middleware/auth/models.py +2 -2
- kailash/middleware/database/base_models.py +1 -7
- kailash/middleware/gateway/__init__.py +22 -0
- kailash/middleware/gateway/checkpoint_manager.py +398 -0
- kailash/middleware/gateway/deduplicator.py +382 -0
- kailash/middleware/gateway/durable_gateway.py +417 -0
- kailash/middleware/gateway/durable_request.py +498 -0
- kailash/middleware/gateway/event_store.py +459 -0
- kailash/nodes/admin/permission_check.py +817 -33
- kailash/nodes/admin/role_management.py +1242 -108
- kailash/nodes/admin/schema_manager.py +438 -0
- kailash/nodes/admin/user_management.py +1124 -1582
- kailash/nodes/code/__init__.py +8 -1
- kailash/nodes/code/async_python.py +1035 -0
- kailash/nodes/code/python.py +1 -0
- kailash/nodes/data/async_sql.py +9 -3
- kailash/nodes/data/sql.py +20 -11
- kailash/nodes/data/workflow_connection_pool.py +643 -0
- kailash/nodes/rag/__init__.py +1 -4
- kailash/resources/__init__.py +40 -0
- kailash/resources/factory.py +533 -0
- kailash/resources/health.py +319 -0
- kailash/resources/reference.py +288 -0
- kailash/resources/registry.py +392 -0
- kailash/runtime/async_local.py +711 -302
- kailash/testing/__init__.py +34 -0
- kailash/testing/async_test_case.py +353 -0
- kailash/testing/async_utils.py +345 -0
- kailash/testing/fixtures.py +458 -0
- kailash/testing/mock_registry.py +495 -0
- kailash/workflow/__init__.py +8 -0
- kailash/workflow/async_builder.py +621 -0
- kailash/workflow/async_patterns.py +766 -0
- kailash/workflow/cyclic_runner.py +107 -16
- kailash/workflow/graph.py +7 -2
- kailash/workflow/resilience.py +11 -1
- {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/METADATA +7 -4
- {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/RECORD +57 -22
- {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/WHEEL +0 -0
- {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/entry_points.txt +0 -0
- {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.5.0.dist-info → kailash-0.6.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,834 @@
|
|
1
|
+
"""Compliance-aware routing for data sovereignty and regulatory requirements."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import json
|
5
|
+
import logging
|
6
|
+
from dataclasses import dataclass
|
7
|
+
from datetime import UTC, datetime, timedelta
|
8
|
+
from enum import Enum
|
9
|
+
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
10
|
+
|
11
|
+
from .location import ComplianceZone, EdgeLocation, EdgeRegion, GeographicCoordinates
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
class DataClassification(Enum):
|
17
|
+
"""Data classification levels for compliance routing."""
|
18
|
+
|
19
|
+
PUBLIC = "public" # No restrictions
|
20
|
+
INTERNAL = "internal" # Organization internal
|
21
|
+
CONFIDENTIAL = "confidential" # Restricted access
|
22
|
+
RESTRICTED = "restricted" # Highly restricted
|
23
|
+
|
24
|
+
# Personal data types
|
25
|
+
PII = "pii" # Personally Identifiable Information
|
26
|
+
PHI = "phi" # Protected Health Information
|
27
|
+
PCI = "pci" # Payment Card Information
|
28
|
+
|
29
|
+
# Industry-specific
|
30
|
+
FINANCIAL = "financial" # Financial data
|
31
|
+
HEALTHCARE = "healthcare" # Healthcare data
|
32
|
+
EDUCATIONAL = "educational" # Educational records
|
33
|
+
|
34
|
+
# Regional data types
|
35
|
+
EU_PERSONAL = "eu_personal" # GDPR-protected data
|
36
|
+
CALIFORNIA_RESIDENT = "california_resident" # CCPA-protected data
|
37
|
+
CANADIAN_PERSONAL = "canadian_personal" # PIPEDA-protected data
|
38
|
+
|
39
|
+
|
40
|
+
class ComplianceRule(Enum):
|
41
|
+
"""Compliance rules and requirements."""
|
42
|
+
|
43
|
+
# Geographic restrictions
|
44
|
+
DATA_RESIDENCY = "data_residency" # Data must stay in specific region
|
45
|
+
CROSS_BORDER_TRANSFER = "cross_border_transfer" # Restrictions on transfers
|
46
|
+
|
47
|
+
# Encryption requirements
|
48
|
+
ENCRYPTION_AT_REST = "encryption_at_rest"
|
49
|
+
ENCRYPTION_IN_TRANSIT = "encryption_in_transit"
|
50
|
+
KEY_MANAGEMENT = "key_management"
|
51
|
+
|
52
|
+
# Access controls
|
53
|
+
RBAC_REQUIRED = "rbac_required" # Role-based access control
|
54
|
+
MFA_REQUIRED = "mfa_required" # Multi-factor authentication
|
55
|
+
AUDIT_LOGGING = "audit_logging"
|
56
|
+
|
57
|
+
# Data lifecycle
|
58
|
+
RETENTION_PERIOD = "retention_period"
|
59
|
+
RIGHT_TO_DELETE = "right_to_delete" # GDPR Article 17
|
60
|
+
DATA_PORTABILITY = "data_portability" # GDPR Article 20
|
61
|
+
|
62
|
+
# Industry-specific
|
63
|
+
HIPAA_SAFEGUARDS = "hipaa_safeguards"
|
64
|
+
SOX_CONTROLS = "sox_controls"
|
65
|
+
PCI_DSS_REQUIREMENTS = "pci_dss_requirements"
|
66
|
+
|
67
|
+
|
68
|
+
@dataclass
|
69
|
+
class ComplianceRequirement:
|
70
|
+
"""Specific compliance requirement with enforcement details."""
|
71
|
+
|
72
|
+
rule: ComplianceRule
|
73
|
+
description: str
|
74
|
+
enforcement_level: str # "required", "recommended", "optional"
|
75
|
+
applicable_data_types: List[DataClassification]
|
76
|
+
applicable_regions: List[EdgeRegion] = None
|
77
|
+
exceptions: List[str] = None
|
78
|
+
|
79
|
+
def __post_init__(self):
|
80
|
+
if self.applicable_regions is None:
|
81
|
+
self.applicable_regions = []
|
82
|
+
if self.exceptions is None:
|
83
|
+
self.exceptions = []
|
84
|
+
|
85
|
+
|
86
|
+
@dataclass
|
87
|
+
class ComplianceContext:
|
88
|
+
"""Context for compliance routing decisions."""
|
89
|
+
|
90
|
+
# Data characteristics
|
91
|
+
data_classification: DataClassification
|
92
|
+
data_size_gb: float = 0.0
|
93
|
+
contains_personal_data: bool = False
|
94
|
+
subject_countries: List[str] = None # ISO country codes
|
95
|
+
|
96
|
+
# User context
|
97
|
+
user_location: Optional[GeographicCoordinates] = None
|
98
|
+
user_citizenship: Optional[str] = None # ISO country code
|
99
|
+
user_residence: Optional[str] = None # ISO country code
|
100
|
+
|
101
|
+
# Operation context
|
102
|
+
operation_type: str = "read" # "read", "write", "process", "store"
|
103
|
+
retention_period_days: Optional[int] = None
|
104
|
+
sharing_scope: str = "internal" # "internal", "third_party", "public"
|
105
|
+
|
106
|
+
# Compliance overrides
|
107
|
+
explicit_compliance_zones: List[ComplianceZone] = None
|
108
|
+
override_data_residency: bool = False
|
109
|
+
|
110
|
+
def __post_init__(self):
|
111
|
+
if self.subject_countries is None:
|
112
|
+
self.subject_countries = []
|
113
|
+
if self.explicit_compliance_zones is None:
|
114
|
+
self.explicit_compliance_zones = []
|
115
|
+
|
116
|
+
|
117
|
+
@dataclass
|
118
|
+
class ComplianceDecision:
|
119
|
+
"""Result of compliance routing decision."""
|
120
|
+
|
121
|
+
allowed_locations: List[EdgeLocation]
|
122
|
+
prohibited_locations: List[EdgeLocation]
|
123
|
+
recommended_location: Optional[EdgeLocation]
|
124
|
+
|
125
|
+
# Decision reasoning
|
126
|
+
compliance_requirements: List[ComplianceRequirement]
|
127
|
+
applied_rules: List[str]
|
128
|
+
warnings: List[str]
|
129
|
+
violations: List[str]
|
130
|
+
|
131
|
+
# Metadata
|
132
|
+
decision_timestamp: datetime
|
133
|
+
decision_confidence: float # 0.0 to 1.0
|
134
|
+
|
135
|
+
def __post_init__(self):
|
136
|
+
if self.decision_timestamp is None:
|
137
|
+
self.decision_timestamp = datetime.now(UTC)
|
138
|
+
|
139
|
+
|
140
|
+
class ComplianceRouter:
|
141
|
+
"""Compliance-aware router for data sovereignty and regulatory requirements.
|
142
|
+
|
143
|
+
Routes data processing requests to appropriate edge locations based on
|
144
|
+
regulatory compliance requirements and data classification.
|
145
|
+
"""
|
146
|
+
|
147
|
+
def __init__(self):
|
148
|
+
"""Initialize compliance router with standard rules."""
|
149
|
+
self.compliance_rules = self._load_default_compliance_rules()
|
150
|
+
self.country_to_region_mapping = self._load_country_region_mapping()
|
151
|
+
self.audit_log: List[Dict] = []
|
152
|
+
|
153
|
+
logger.info("Initialized ComplianceRouter with standard compliance rules")
|
154
|
+
|
155
|
+
def _load_default_compliance_rules(
|
156
|
+
self,
|
157
|
+
) -> Dict[ComplianceZone, List[ComplianceRequirement]]:
|
158
|
+
"""Load default compliance rules for each zone."""
|
159
|
+
rules = {
|
160
|
+
ComplianceZone.GDPR: [
|
161
|
+
ComplianceRequirement(
|
162
|
+
rule=ComplianceRule.DATA_RESIDENCY,
|
163
|
+
description="Personal data of EU residents must be processed within EU/EEA",
|
164
|
+
enforcement_level="required",
|
165
|
+
applicable_data_types=[
|
166
|
+
DataClassification.PII,
|
167
|
+
DataClassification.EU_PERSONAL,
|
168
|
+
],
|
169
|
+
applicable_regions=[
|
170
|
+
EdgeRegion.EU_WEST,
|
171
|
+
EdgeRegion.EU_CENTRAL,
|
172
|
+
EdgeRegion.EU_NORTH,
|
173
|
+
],
|
174
|
+
),
|
175
|
+
ComplianceRequirement(
|
176
|
+
rule=ComplianceRule.RIGHT_TO_DELETE,
|
177
|
+
description="Data subjects have right to erasure (Article 17)",
|
178
|
+
enforcement_level="required",
|
179
|
+
applicable_data_types=[
|
180
|
+
DataClassification.PII,
|
181
|
+
DataClassification.EU_PERSONAL,
|
182
|
+
],
|
183
|
+
),
|
184
|
+
ComplianceRequirement(
|
185
|
+
rule=ComplianceRule.ENCRYPTION_AT_REST,
|
186
|
+
description="Personal data must be encrypted at rest",
|
187
|
+
enforcement_level="required",
|
188
|
+
applicable_data_types=[
|
189
|
+
DataClassification.PII,
|
190
|
+
DataClassification.EU_PERSONAL,
|
191
|
+
],
|
192
|
+
),
|
193
|
+
ComplianceRequirement(
|
194
|
+
rule=ComplianceRule.AUDIT_LOGGING,
|
195
|
+
description="All data processing must be logged for accountability",
|
196
|
+
enforcement_level="required",
|
197
|
+
applicable_data_types=[
|
198
|
+
DataClassification.PII,
|
199
|
+
DataClassification.EU_PERSONAL,
|
200
|
+
],
|
201
|
+
),
|
202
|
+
],
|
203
|
+
ComplianceZone.CCPA: [
|
204
|
+
ComplianceRequirement(
|
205
|
+
rule=ComplianceRule.DATA_RESIDENCY,
|
206
|
+
description="California residents' data should be processed in compliant facilities",
|
207
|
+
enforcement_level="recommended",
|
208
|
+
applicable_data_types=[DataClassification.CALIFORNIA_RESIDENT],
|
209
|
+
applicable_regions=[EdgeRegion.US_WEST],
|
210
|
+
),
|
211
|
+
ComplianceRequirement(
|
212
|
+
rule=ComplianceRule.RIGHT_TO_DELETE,
|
213
|
+
description="Consumers have right to delete personal information",
|
214
|
+
enforcement_level="required",
|
215
|
+
applicable_data_types=[DataClassification.CALIFORNIA_RESIDENT],
|
216
|
+
),
|
217
|
+
ComplianceRequirement(
|
218
|
+
rule=ComplianceRule.DATA_PORTABILITY,
|
219
|
+
description="Consumers have right to data portability",
|
220
|
+
enforcement_level="required",
|
221
|
+
applicable_data_types=[DataClassification.CALIFORNIA_RESIDENT],
|
222
|
+
),
|
223
|
+
],
|
224
|
+
ComplianceZone.HIPAA: [
|
225
|
+
ComplianceRequirement(
|
226
|
+
rule=ComplianceRule.ENCRYPTION_AT_REST,
|
227
|
+
description="PHI must be encrypted at rest",
|
228
|
+
enforcement_level="required",
|
229
|
+
applicable_data_types=[
|
230
|
+
DataClassification.PHI,
|
231
|
+
DataClassification.HEALTHCARE,
|
232
|
+
],
|
233
|
+
),
|
234
|
+
ComplianceRequirement(
|
235
|
+
rule=ComplianceRule.ENCRYPTION_IN_TRANSIT,
|
236
|
+
description="PHI must be encrypted in transit",
|
237
|
+
enforcement_level="required",
|
238
|
+
applicable_data_types=[
|
239
|
+
DataClassification.PHI,
|
240
|
+
DataClassification.HEALTHCARE,
|
241
|
+
],
|
242
|
+
),
|
243
|
+
ComplianceRequirement(
|
244
|
+
rule=ComplianceRule.AUDIT_LOGGING,
|
245
|
+
description="All PHI access must be logged",
|
246
|
+
enforcement_level="required",
|
247
|
+
applicable_data_types=[
|
248
|
+
DataClassification.PHI,
|
249
|
+
DataClassification.HEALTHCARE,
|
250
|
+
],
|
251
|
+
),
|
252
|
+
ComplianceRequirement(
|
253
|
+
rule=ComplianceRule.MFA_REQUIRED,
|
254
|
+
description="Multi-factor authentication required for PHI access",
|
255
|
+
enforcement_level="required",
|
256
|
+
applicable_data_types=[
|
257
|
+
DataClassification.PHI,
|
258
|
+
DataClassification.HEALTHCARE,
|
259
|
+
],
|
260
|
+
),
|
261
|
+
],
|
262
|
+
ComplianceZone.PCI_DSS: [
|
263
|
+
ComplianceRequirement(
|
264
|
+
rule=ComplianceRule.ENCRYPTION_AT_REST,
|
265
|
+
description="Cardholder data must be encrypted at rest",
|
266
|
+
enforcement_level="required",
|
267
|
+
applicable_data_types=[DataClassification.PCI],
|
268
|
+
),
|
269
|
+
ComplianceRequirement(
|
270
|
+
rule=ComplianceRule.ENCRYPTION_IN_TRANSIT,
|
271
|
+
description="Cardholder data must be encrypted in transit",
|
272
|
+
enforcement_level="required",
|
273
|
+
applicable_data_types=[DataClassification.PCI],
|
274
|
+
),
|
275
|
+
ComplianceRequirement(
|
276
|
+
rule=ComplianceRule.RBAC_REQUIRED,
|
277
|
+
description="Role-based access control required",
|
278
|
+
enforcement_level="required",
|
279
|
+
applicable_data_types=[DataClassification.PCI],
|
280
|
+
),
|
281
|
+
],
|
282
|
+
ComplianceZone.SOX: [
|
283
|
+
ComplianceRequirement(
|
284
|
+
rule=ComplianceRule.AUDIT_LOGGING,
|
285
|
+
description="All financial data access must be logged",
|
286
|
+
enforcement_level="required",
|
287
|
+
applicable_data_types=[DataClassification.FINANCIAL],
|
288
|
+
),
|
289
|
+
ComplianceRequirement(
|
290
|
+
rule=ComplianceRule.RETENTION_PERIOD,
|
291
|
+
description="Financial records must be retained for 7 years",
|
292
|
+
enforcement_level="required",
|
293
|
+
applicable_data_types=[DataClassification.FINANCIAL],
|
294
|
+
),
|
295
|
+
],
|
296
|
+
ComplianceZone.PUBLIC: [
|
297
|
+
ComplianceRequirement(
|
298
|
+
rule=ComplianceRule.ENCRYPTION_IN_TRANSIT,
|
299
|
+
description="Data should be encrypted in transit",
|
300
|
+
enforcement_level="recommended",
|
301
|
+
applicable_data_types=[
|
302
|
+
DataClassification.PUBLIC,
|
303
|
+
DataClassification.INTERNAL,
|
304
|
+
],
|
305
|
+
)
|
306
|
+
],
|
307
|
+
}
|
308
|
+
|
309
|
+
return rules
|
310
|
+
|
311
|
+
def _load_country_region_mapping(self) -> Dict[str, EdgeRegion]:
|
312
|
+
"""Load mapping of ISO country codes to edge regions."""
|
313
|
+
return {
|
314
|
+
# North America
|
315
|
+
"US": EdgeRegion.US_EAST, # Default US region
|
316
|
+
"CA": EdgeRegion.CANADA,
|
317
|
+
# Europe
|
318
|
+
"DE": EdgeRegion.EU_CENTRAL,
|
319
|
+
"FR": EdgeRegion.EU_WEST,
|
320
|
+
"GB": EdgeRegion.UK,
|
321
|
+
"IE": EdgeRegion.EU_WEST,
|
322
|
+
"NL": EdgeRegion.EU_WEST,
|
323
|
+
"ES": EdgeRegion.EU_WEST,
|
324
|
+
"IT": EdgeRegion.EU_WEST,
|
325
|
+
"SE": EdgeRegion.EU_NORTH,
|
326
|
+
"NO": EdgeRegion.EU_NORTH,
|
327
|
+
"FI": EdgeRegion.EU_NORTH,
|
328
|
+
"DK": EdgeRegion.EU_NORTH,
|
329
|
+
# Asia Pacific
|
330
|
+
"JP": EdgeRegion.JAPAN,
|
331
|
+
"SG": EdgeRegion.ASIA_SOUTHEAST,
|
332
|
+
"AU": EdgeRegion.AUSTRALIA,
|
333
|
+
"KR": EdgeRegion.ASIA_EAST,
|
334
|
+
"HK": EdgeRegion.ASIA_EAST,
|
335
|
+
"IN": EdgeRegion.ASIA_SOUTH,
|
336
|
+
"TH": EdgeRegion.ASIA_SOUTHEAST,
|
337
|
+
"MY": EdgeRegion.ASIA_SOUTHEAST,
|
338
|
+
"ID": EdgeRegion.ASIA_SOUTHEAST,
|
339
|
+
# Other regions
|
340
|
+
"BR": EdgeRegion.SOUTH_AMERICA,
|
341
|
+
"MX": EdgeRegion.SOUTH_AMERICA,
|
342
|
+
"ZA": EdgeRegion.AFRICA,
|
343
|
+
"AE": EdgeRegion.MIDDLE_EAST,
|
344
|
+
"SA": EdgeRegion.MIDDLE_EAST,
|
345
|
+
}
|
346
|
+
|
347
|
+
async def route_compliant(
|
348
|
+
self, context: ComplianceContext, available_locations: List[EdgeLocation]
|
349
|
+
) -> ComplianceDecision:
|
350
|
+
"""Route data processing to compliant edge locations.
|
351
|
+
|
352
|
+
Args:
|
353
|
+
context: Compliance context with data and user information
|
354
|
+
available_locations: List of available edge locations
|
355
|
+
|
356
|
+
Returns:
|
357
|
+
Compliance decision with allowed/prohibited locations
|
358
|
+
"""
|
359
|
+
logger.info(
|
360
|
+
f"Routing compliance decision for {context.data_classification.value} data"
|
361
|
+
)
|
362
|
+
|
363
|
+
# Determine applicable compliance zones
|
364
|
+
applicable_zones = self._determine_applicable_zones(context)
|
365
|
+
|
366
|
+
# Get compliance requirements
|
367
|
+
requirements = self._get_compliance_requirements(applicable_zones, context)
|
368
|
+
|
369
|
+
# Evaluate each location
|
370
|
+
allowed_locations = []
|
371
|
+
prohibited_locations = []
|
372
|
+
violations = []
|
373
|
+
warnings = []
|
374
|
+
applied_rules = []
|
375
|
+
|
376
|
+
for location in available_locations:
|
377
|
+
evaluation = await self._evaluate_location_compliance(
|
378
|
+
location, context, requirements
|
379
|
+
)
|
380
|
+
|
381
|
+
if evaluation["compliant"]:
|
382
|
+
allowed_locations.append(location)
|
383
|
+
else:
|
384
|
+
prohibited_locations.append(location)
|
385
|
+
violations.extend(evaluation["violations"])
|
386
|
+
|
387
|
+
warnings.extend(evaluation["warnings"])
|
388
|
+
applied_rules.extend(evaluation["applied_rules"])
|
389
|
+
|
390
|
+
# Select recommended location
|
391
|
+
recommended_location = self._select_recommended_location(
|
392
|
+
allowed_locations, context, requirements
|
393
|
+
)
|
394
|
+
|
395
|
+
# Calculate decision confidence
|
396
|
+
confidence = self._calculate_decision_confidence(
|
397
|
+
len(allowed_locations), len(prohibited_locations), len(violations)
|
398
|
+
)
|
399
|
+
|
400
|
+
decision = ComplianceDecision(
|
401
|
+
allowed_locations=allowed_locations,
|
402
|
+
prohibited_locations=prohibited_locations,
|
403
|
+
recommended_location=recommended_location,
|
404
|
+
compliance_requirements=requirements,
|
405
|
+
applied_rules=list(set(applied_rules)),
|
406
|
+
warnings=list(set(warnings)),
|
407
|
+
violations=list(set(violations)),
|
408
|
+
decision_timestamp=datetime.now(UTC),
|
409
|
+
decision_confidence=confidence,
|
410
|
+
)
|
411
|
+
|
412
|
+
# Log decision for audit trail
|
413
|
+
await self._log_compliance_decision(context, decision)
|
414
|
+
|
415
|
+
logger.info(
|
416
|
+
f"Compliance routing: {len(allowed_locations)} allowed, "
|
417
|
+
f"{len(prohibited_locations)} prohibited locations"
|
418
|
+
)
|
419
|
+
|
420
|
+
return decision
|
421
|
+
|
422
|
+
def _determine_applicable_zones(
|
423
|
+
self, context: ComplianceContext
|
424
|
+
) -> List[ComplianceZone]:
|
425
|
+
"""Determine which compliance zones apply to the data and context."""
|
426
|
+
zones = []
|
427
|
+
|
428
|
+
# Explicit zones override everything
|
429
|
+
if context.explicit_compliance_zones:
|
430
|
+
return context.explicit_compliance_zones
|
431
|
+
|
432
|
+
# Data classification-based zones
|
433
|
+
if context.data_classification in [
|
434
|
+
DataClassification.PII,
|
435
|
+
DataClassification.EU_PERSONAL,
|
436
|
+
]:
|
437
|
+
# Check if EU resident data
|
438
|
+
if any(
|
439
|
+
country
|
440
|
+
in [
|
441
|
+
"DE",
|
442
|
+
"FR",
|
443
|
+
"IT",
|
444
|
+
"ES",
|
445
|
+
"NL",
|
446
|
+
"BE",
|
447
|
+
"AT",
|
448
|
+
"SE",
|
449
|
+
"DK",
|
450
|
+
"FI",
|
451
|
+
"IE",
|
452
|
+
"PT",
|
453
|
+
"GR",
|
454
|
+
"LU",
|
455
|
+
"MT",
|
456
|
+
"CY",
|
457
|
+
"EE",
|
458
|
+
"LV",
|
459
|
+
"LT",
|
460
|
+
"SI",
|
461
|
+
"SK",
|
462
|
+
"HR",
|
463
|
+
"BG",
|
464
|
+
"RO",
|
465
|
+
"HU",
|
466
|
+
"CZ",
|
467
|
+
"PL",
|
468
|
+
]
|
469
|
+
for country in context.subject_countries
|
470
|
+
):
|
471
|
+
zones.append(ComplianceZone.GDPR)
|
472
|
+
|
473
|
+
if context.data_classification == DataClassification.CALIFORNIA_RESIDENT:
|
474
|
+
zones.append(ComplianceZone.CCPA)
|
475
|
+
|
476
|
+
if context.data_classification in [
|
477
|
+
DataClassification.PHI,
|
478
|
+
DataClassification.HEALTHCARE,
|
479
|
+
]:
|
480
|
+
zones.append(ComplianceZone.HIPAA)
|
481
|
+
|
482
|
+
if context.data_classification == DataClassification.PCI:
|
483
|
+
zones.append(ComplianceZone.PCI_DSS)
|
484
|
+
|
485
|
+
if context.data_classification == DataClassification.FINANCIAL:
|
486
|
+
zones.append(ComplianceZone.SOX)
|
487
|
+
|
488
|
+
# Geographic-based zones
|
489
|
+
if context.user_location:
|
490
|
+
# Add region-specific compliance based on user location
|
491
|
+
pass # Could add geo-based logic here
|
492
|
+
|
493
|
+
# Default to public if no specific zones identified
|
494
|
+
if not zones:
|
495
|
+
zones.append(ComplianceZone.PUBLIC)
|
496
|
+
|
497
|
+
return zones
|
498
|
+
|
499
|
+
def _get_compliance_requirements(
|
500
|
+
self, zones: List[ComplianceZone], context: ComplianceContext
|
501
|
+
) -> List[ComplianceRequirement]:
|
502
|
+
"""Get all compliance requirements for the given zones and context."""
|
503
|
+
requirements = []
|
504
|
+
|
505
|
+
for zone in zones:
|
506
|
+
zone_requirements = self.compliance_rules.get(zone, [])
|
507
|
+
for requirement in zone_requirements:
|
508
|
+
# Check if requirement applies to this data type
|
509
|
+
if context.data_classification in requirement.applicable_data_types:
|
510
|
+
requirements.append(requirement)
|
511
|
+
|
512
|
+
return requirements
|
513
|
+
|
514
|
+
async def _evaluate_location_compliance(
|
515
|
+
self,
|
516
|
+
location: EdgeLocation,
|
517
|
+
context: ComplianceContext,
|
518
|
+
requirements: List[ComplianceRequirement],
|
519
|
+
) -> Dict[str, Any]:
|
520
|
+
"""Evaluate if a location meets compliance requirements."""
|
521
|
+
compliant = True
|
522
|
+
violations = []
|
523
|
+
warnings = []
|
524
|
+
applied_rules = []
|
525
|
+
|
526
|
+
for requirement in requirements:
|
527
|
+
rule_result = await self._evaluate_compliance_rule(
|
528
|
+
location, context, requirement
|
529
|
+
)
|
530
|
+
|
531
|
+
applied_rules.append(requirement.rule.value)
|
532
|
+
|
533
|
+
if not rule_result["compliant"]:
|
534
|
+
if requirement.enforcement_level == "required":
|
535
|
+
compliant = False
|
536
|
+
violations.append(rule_result["message"])
|
537
|
+
else:
|
538
|
+
warnings.append(rule_result["message"])
|
539
|
+
|
540
|
+
return {
|
541
|
+
"compliant": compliant,
|
542
|
+
"violations": violations,
|
543
|
+
"warnings": warnings,
|
544
|
+
"applied_rules": applied_rules,
|
545
|
+
}
|
546
|
+
|
547
|
+
async def _evaluate_compliance_rule(
|
548
|
+
self,
|
549
|
+
location: EdgeLocation,
|
550
|
+
context: ComplianceContext,
|
551
|
+
requirement: ComplianceRequirement,
|
552
|
+
) -> Dict[str, Any]:
|
553
|
+
"""Evaluate a specific compliance rule for a location."""
|
554
|
+
rule = requirement.rule
|
555
|
+
|
556
|
+
if rule == ComplianceRule.DATA_RESIDENCY:
|
557
|
+
return await self._check_data_residency(location, context, requirement)
|
558
|
+
elif rule == ComplianceRule.ENCRYPTION_AT_REST:
|
559
|
+
return self._check_encryption_at_rest(location, requirement)
|
560
|
+
elif rule == ComplianceRule.ENCRYPTION_IN_TRANSIT:
|
561
|
+
return self._check_encryption_in_transit(location, requirement)
|
562
|
+
elif rule == ComplianceRule.AUDIT_LOGGING:
|
563
|
+
return self._check_audit_logging(location, requirement)
|
564
|
+
elif rule == ComplianceRule.MFA_REQUIRED:
|
565
|
+
return self._check_mfa_support(location, requirement)
|
566
|
+
elif rule == ComplianceRule.RBAC_REQUIRED:
|
567
|
+
return self._check_rbac_support(location, requirement)
|
568
|
+
else:
|
569
|
+
# Default pass for unimplemented rules
|
570
|
+
return {"compliant": True, "message": f"Rule {rule.value} not evaluated"}
|
571
|
+
|
572
|
+
async def _check_data_residency(
|
573
|
+
self,
|
574
|
+
location: EdgeLocation,
|
575
|
+
context: ComplianceContext,
|
576
|
+
requirement: ComplianceRequirement,
|
577
|
+
) -> Dict[str, Any]:
|
578
|
+
"""Check data residency compliance."""
|
579
|
+
if context.override_data_residency:
|
580
|
+
return {
|
581
|
+
"compliant": True,
|
582
|
+
"message": "Data residency requirement overridden",
|
583
|
+
}
|
584
|
+
|
585
|
+
# Check if location region is in allowed regions
|
586
|
+
if requirement.applicable_regions:
|
587
|
+
if location.region not in requirement.applicable_regions:
|
588
|
+
return {
|
589
|
+
"compliant": False,
|
590
|
+
"message": f"Data residency violation: {location.region.value} not in allowed regions {[r.value for r in requirement.applicable_regions]}",
|
591
|
+
}
|
592
|
+
|
593
|
+
return {"compliant": True, "message": "Data residency requirement met"}
|
594
|
+
|
595
|
+
def _check_encryption_at_rest(
|
596
|
+
self, location: EdgeLocation, requirement: ComplianceRequirement
|
597
|
+
) -> Dict[str, Any]:
|
598
|
+
"""Check encryption at rest support."""
|
599
|
+
if location.capabilities.encryption_at_rest:
|
600
|
+
return {"compliant": True, "message": "Encryption at rest supported"}
|
601
|
+
else:
|
602
|
+
return {"compliant": False, "message": "Encryption at rest not supported"}
|
603
|
+
|
604
|
+
def _check_encryption_in_transit(
|
605
|
+
self, location: EdgeLocation, requirement: ComplianceRequirement
|
606
|
+
) -> Dict[str, Any]:
|
607
|
+
"""Check encryption in transit support."""
|
608
|
+
if location.capabilities.encryption_in_transit:
|
609
|
+
return {"compliant": True, "message": "Encryption in transit supported"}
|
610
|
+
else:
|
611
|
+
return {
|
612
|
+
"compliant": False,
|
613
|
+
"message": "Encryption in transit not supported",
|
614
|
+
}
|
615
|
+
|
616
|
+
def _check_audit_logging(
|
617
|
+
self, location: EdgeLocation, requirement: ComplianceRequirement
|
618
|
+
) -> Dict[str, Any]:
|
619
|
+
"""Check audit logging support."""
|
620
|
+
if location.capabilities.audit_logging:
|
621
|
+
return {"compliant": True, "message": "Audit logging supported"}
|
622
|
+
else:
|
623
|
+
return {"compliant": False, "message": "Audit logging not supported"}
|
624
|
+
|
625
|
+
def _check_mfa_support(
|
626
|
+
self, location: EdgeLocation, requirement: ComplianceRequirement
|
627
|
+
) -> Dict[str, Any]:
|
628
|
+
"""Check multi-factor authentication support."""
|
629
|
+
# For now, assume all locations support MFA
|
630
|
+
return {"compliant": True, "message": "MFA support available"}
|
631
|
+
|
632
|
+
def _check_rbac_support(
|
633
|
+
self, location: EdgeLocation, requirement: ComplianceRequirement
|
634
|
+
) -> Dict[str, Any]:
|
635
|
+
"""Check role-based access control support."""
|
636
|
+
# For now, assume all locations support RBAC
|
637
|
+
return {"compliant": True, "message": "RBAC support available"}
|
638
|
+
|
639
|
+
def _select_recommended_location(
|
640
|
+
self,
|
641
|
+
allowed_locations: List[EdgeLocation],
|
642
|
+
context: ComplianceContext,
|
643
|
+
requirements: List[ComplianceRequirement],
|
644
|
+
) -> Optional[EdgeLocation]:
|
645
|
+
"""Select the best recommended location from allowed locations."""
|
646
|
+
if not allowed_locations:
|
647
|
+
return None
|
648
|
+
|
649
|
+
if len(allowed_locations) == 1:
|
650
|
+
return allowed_locations[0]
|
651
|
+
|
652
|
+
# Simple scoring: prefer locations with better compliance coverage
|
653
|
+
def score_location(location):
|
654
|
+
score = 0
|
655
|
+
|
656
|
+
# Prefer locations with more compliance zones
|
657
|
+
score += len(location.compliance_zones) * 10
|
658
|
+
|
659
|
+
# Prefer locations closer to user (if known)
|
660
|
+
if context.user_location:
|
661
|
+
distance = location.coordinates.distance_to(context.user_location)
|
662
|
+
score += max(0, 1000 - distance) # Closer = higher score
|
663
|
+
|
664
|
+
# Prefer locations with better health
|
665
|
+
if location.is_healthy:
|
666
|
+
score += 100
|
667
|
+
|
668
|
+
# Prefer locations with lower load
|
669
|
+
score += (1.0 - location.get_load_factor()) * 50
|
670
|
+
|
671
|
+
return score
|
672
|
+
|
673
|
+
# Return location with highest score
|
674
|
+
return max(allowed_locations, key=score_location)
|
675
|
+
|
676
|
+
def _calculate_decision_confidence(
|
677
|
+
self, allowed_count: int, prohibited_count: int, violation_count: int
|
678
|
+
) -> float:
|
679
|
+
"""Calculate confidence in the compliance decision."""
|
680
|
+
total_locations = allowed_count + prohibited_count
|
681
|
+
|
682
|
+
if total_locations == 0:
|
683
|
+
return 0.0
|
684
|
+
|
685
|
+
# Base confidence on ratio of allowed to total
|
686
|
+
base_confidence = allowed_count / total_locations
|
687
|
+
|
688
|
+
# Reduce confidence if violations found
|
689
|
+
violation_penalty = min(0.3, violation_count * 0.1)
|
690
|
+
|
691
|
+
# Ensure we have at least one allowed location for high confidence
|
692
|
+
if allowed_count == 0:
|
693
|
+
confidence = 0.0
|
694
|
+
elif allowed_count >= 3:
|
695
|
+
confidence = min(1.0, base_confidence - violation_penalty + 0.2)
|
696
|
+
else:
|
697
|
+
confidence = base_confidence - violation_penalty
|
698
|
+
|
699
|
+
return max(0.0, min(1.0, confidence))
|
700
|
+
|
701
|
+
async def _log_compliance_decision(
|
702
|
+
self, context: ComplianceContext, decision: ComplianceDecision
|
703
|
+
):
|
704
|
+
"""Log compliance decision for audit trail."""
|
705
|
+
audit_entry = {
|
706
|
+
"timestamp": decision.decision_timestamp.isoformat(),
|
707
|
+
"data_classification": context.data_classification.value,
|
708
|
+
"operation_type": context.operation_type,
|
709
|
+
"allowed_locations": [
|
710
|
+
loc.location_id for loc in decision.allowed_locations
|
711
|
+
],
|
712
|
+
"prohibited_locations": [
|
713
|
+
loc.location_id for loc in decision.prohibited_locations
|
714
|
+
],
|
715
|
+
"recommended_location": (
|
716
|
+
decision.recommended_location.location_id
|
717
|
+
if decision.recommended_location
|
718
|
+
else None
|
719
|
+
),
|
720
|
+
"violations": decision.violations,
|
721
|
+
"confidence": decision.decision_confidence,
|
722
|
+
"compliance_zones": (
|
723
|
+
[zone.value for zone in context.explicit_compliance_zones]
|
724
|
+
if context.explicit_compliance_zones
|
725
|
+
else []
|
726
|
+
),
|
727
|
+
"subject_countries": context.subject_countries,
|
728
|
+
}
|
729
|
+
|
730
|
+
self.audit_log.append(audit_entry)
|
731
|
+
|
732
|
+
# Keep only last 1000 entries to prevent memory growth
|
733
|
+
if len(self.audit_log) > 1000:
|
734
|
+
self.audit_log = self.audit_log[-1000:]
|
735
|
+
|
736
|
+
def classify_data(self, data: Dict[str, Any]) -> DataClassification:
|
737
|
+
"""Automatically classify data based on content patterns."""
|
738
|
+
# Simple pattern-based classification
|
739
|
+
data_str = json.dumps(data).lower()
|
740
|
+
|
741
|
+
# Check for PII patterns
|
742
|
+
pii_patterns = ["email", "ssn", "social_security", "phone", "address", "name"]
|
743
|
+
if any(pattern in data_str for pattern in pii_patterns):
|
744
|
+
return DataClassification.PII
|
745
|
+
|
746
|
+
# Check for healthcare patterns
|
747
|
+
health_patterns = ["medical", "diagnosis", "treatment", "patient", "health"]
|
748
|
+
if any(pattern in data_str for pattern in health_patterns):
|
749
|
+
return DataClassification.PHI
|
750
|
+
|
751
|
+
# Check for payment patterns
|
752
|
+
payment_patterns = ["credit_card", "card_number", "cvv", "payment", "billing"]
|
753
|
+
if any(pattern in data_str for pattern in payment_patterns):
|
754
|
+
return DataClassification.PCI
|
755
|
+
|
756
|
+
# Check for financial patterns (but not payment patterns)
|
757
|
+
financial_patterns = ["account_number", "routing", "bank", "financial"]
|
758
|
+
if any(pattern in data_str for pattern in financial_patterns) and not any(
|
759
|
+
pattern in data_str for pattern in payment_patterns
|
760
|
+
):
|
761
|
+
return DataClassification.FINANCIAL
|
762
|
+
|
763
|
+
# Default to public
|
764
|
+
return DataClassification.PUBLIC
|
765
|
+
|
766
|
+
def get_applicable_regulations(
|
767
|
+
self, data_classification: DataClassification
|
768
|
+
) -> List[ComplianceZone]:
|
769
|
+
"""Get applicable regulations for a data classification."""
|
770
|
+
regulations = []
|
771
|
+
|
772
|
+
classification_mapping = {
|
773
|
+
DataClassification.PII: [ComplianceZone.GDPR, ComplianceZone.CCPA],
|
774
|
+
DataClassification.EU_PERSONAL: [ComplianceZone.GDPR],
|
775
|
+
DataClassification.CALIFORNIA_RESIDENT: [ComplianceZone.CCPA],
|
776
|
+
DataClassification.PHI: [ComplianceZone.HIPAA],
|
777
|
+
DataClassification.PCI: [ComplianceZone.PCI_DSS],
|
778
|
+
DataClassification.FINANCIAL: [ComplianceZone.SOX],
|
779
|
+
DataClassification.PUBLIC: [ComplianceZone.PUBLIC],
|
780
|
+
}
|
781
|
+
|
782
|
+
return classification_mapping.get(data_classification, [ComplianceZone.PUBLIC])
|
783
|
+
|
784
|
+
def get_audit_log(self, limit: int = 100) -> List[Dict[str, Any]]:
|
785
|
+
"""Get recent compliance decisions from audit log."""
|
786
|
+
return self.audit_log[-limit:]
|
787
|
+
|
788
|
+
def get_compliance_summary(self) -> Dict[str, Any]:
|
789
|
+
"""Get summary of compliance decisions and performance."""
|
790
|
+
if not self.audit_log:
|
791
|
+
return {
|
792
|
+
"total_decisions": 0,
|
793
|
+
"compliance_rate": 1.0,
|
794
|
+
"common_violations": [],
|
795
|
+
"data_classifications": {},
|
796
|
+
}
|
797
|
+
|
798
|
+
total_decisions = len(self.audit_log)
|
799
|
+
decisions_with_violations = sum(
|
800
|
+
1 for entry in self.audit_log if entry["violations"]
|
801
|
+
)
|
802
|
+
compliance_rate = 1.0 - (decisions_with_violations / total_decisions)
|
803
|
+
|
804
|
+
# Analyze common violations
|
805
|
+
all_violations = []
|
806
|
+
for entry in self.audit_log:
|
807
|
+
all_violations.extend(entry["violations"])
|
808
|
+
|
809
|
+
violation_counts = {}
|
810
|
+
for violation in all_violations:
|
811
|
+
violation_counts[violation] = violation_counts.get(violation, 0) + 1
|
812
|
+
|
813
|
+
common_violations = sorted(
|
814
|
+
violation_counts.items(), key=lambda x: x[1], reverse=True
|
815
|
+
)[:5]
|
816
|
+
|
817
|
+
# Analyze data classifications
|
818
|
+
classification_counts = {}
|
819
|
+
for entry in self.audit_log:
|
820
|
+
classification = entry["data_classification"]
|
821
|
+
classification_counts[classification] = (
|
822
|
+
classification_counts.get(classification, 0) + 1
|
823
|
+
)
|
824
|
+
|
825
|
+
return {
|
826
|
+
"total_decisions": total_decisions,
|
827
|
+
"compliance_rate": compliance_rate,
|
828
|
+
"common_violations": [
|
829
|
+
{"violation": v[0], "count": v[1]} for v in common_violations
|
830
|
+
],
|
831
|
+
"data_classifications": classification_counts,
|
832
|
+
"average_confidence": sum(entry["confidence"] for entry in self.audit_log)
|
833
|
+
/ total_decisions,
|
834
|
+
}
|