complio 0.1.1__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.
Files changed (79) hide show
  1. CHANGELOG.md +208 -0
  2. README.md +343 -0
  3. complio/__init__.py +48 -0
  4. complio/cli/__init__.py +0 -0
  5. complio/cli/banner.py +87 -0
  6. complio/cli/commands/__init__.py +0 -0
  7. complio/cli/commands/history.py +439 -0
  8. complio/cli/commands/scan.py +700 -0
  9. complio/cli/main.py +115 -0
  10. complio/cli/output.py +338 -0
  11. complio/config/__init__.py +17 -0
  12. complio/config/settings.py +333 -0
  13. complio/connectors/__init__.py +9 -0
  14. complio/connectors/aws/__init__.py +0 -0
  15. complio/connectors/aws/client.py +342 -0
  16. complio/connectors/base.py +135 -0
  17. complio/core/__init__.py +10 -0
  18. complio/core/registry.py +228 -0
  19. complio/core/runner.py +351 -0
  20. complio/py.typed +0 -0
  21. complio/reporters/__init__.py +7 -0
  22. complio/reporters/generator.py +417 -0
  23. complio/tests_library/__init__.py +0 -0
  24. complio/tests_library/base.py +492 -0
  25. complio/tests_library/identity/__init__.py +0 -0
  26. complio/tests_library/identity/access_key_rotation.py +302 -0
  27. complio/tests_library/identity/mfa_enforcement.py +327 -0
  28. complio/tests_library/identity/root_account_protection.py +470 -0
  29. complio/tests_library/infrastructure/__init__.py +0 -0
  30. complio/tests_library/infrastructure/cloudtrail_encryption.py +286 -0
  31. complio/tests_library/infrastructure/cloudtrail_log_validation.py +274 -0
  32. complio/tests_library/infrastructure/cloudtrail_logging.py +400 -0
  33. complio/tests_library/infrastructure/ebs_encryption.py +244 -0
  34. complio/tests_library/infrastructure/ec2_security_groups.py +321 -0
  35. complio/tests_library/infrastructure/iam_password_policy.py +460 -0
  36. complio/tests_library/infrastructure/nacl_security.py +356 -0
  37. complio/tests_library/infrastructure/rds_encryption.py +252 -0
  38. complio/tests_library/infrastructure/s3_encryption.py +301 -0
  39. complio/tests_library/infrastructure/s3_public_access.py +369 -0
  40. complio/tests_library/infrastructure/secrets_manager_encryption.py +248 -0
  41. complio/tests_library/infrastructure/vpc_flow_logs.py +287 -0
  42. complio/tests_library/logging/__init__.py +0 -0
  43. complio/tests_library/logging/cloudwatch_alarms.py +354 -0
  44. complio/tests_library/logging/cloudwatch_logs_encryption.py +281 -0
  45. complio/tests_library/logging/cloudwatch_retention.py +252 -0
  46. complio/tests_library/logging/config_enabled.py +393 -0
  47. complio/tests_library/logging/eventbridge_rules.py +460 -0
  48. complio/tests_library/logging/guardduty_enabled.py +436 -0
  49. complio/tests_library/logging/security_hub_enabled.py +416 -0
  50. complio/tests_library/logging/sns_encryption.py +273 -0
  51. complio/tests_library/network/__init__.py +0 -0
  52. complio/tests_library/network/alb_nlb_security.py +421 -0
  53. complio/tests_library/network/api_gateway_security.py +452 -0
  54. complio/tests_library/network/cloudfront_https.py +332 -0
  55. complio/tests_library/network/direct_connect_security.py +343 -0
  56. complio/tests_library/network/nacl_configuration.py +367 -0
  57. complio/tests_library/network/network_firewall.py +355 -0
  58. complio/tests_library/network/transit_gateway_security.py +318 -0
  59. complio/tests_library/network/vpc_endpoints_security.py +339 -0
  60. complio/tests_library/network/vpn_security.py +333 -0
  61. complio/tests_library/network/waf_configuration.py +428 -0
  62. complio/tests_library/security/__init__.py +0 -0
  63. complio/tests_library/security/kms_key_rotation.py +314 -0
  64. complio/tests_library/storage/__init__.py +0 -0
  65. complio/tests_library/storage/backup_encryption.py +288 -0
  66. complio/tests_library/storage/dynamodb_encryption.py +280 -0
  67. complio/tests_library/storage/efs_encryption.py +257 -0
  68. complio/tests_library/storage/elasticache_encryption.py +370 -0
  69. complio/tests_library/storage/redshift_encryption.py +252 -0
  70. complio/tests_library/storage/s3_versioning.py +264 -0
  71. complio/utils/__init__.py +26 -0
  72. complio/utils/errors.py +179 -0
  73. complio/utils/exceptions.py +151 -0
  74. complio/utils/history.py +243 -0
  75. complio/utils/logger.py +391 -0
  76. complio-0.1.1.dist-info/METADATA +385 -0
  77. complio-0.1.1.dist-info/RECORD +79 -0
  78. complio-0.1.1.dist-info/WHEEL +4 -0
  79. complio-0.1.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,286 @@
1
+ """
2
+ CloudTrail log encryption compliance test.
3
+
4
+ Checks that all CloudTrail trails have KMS encryption enabled for log files.
5
+
6
+ ISO 27001 Control: A.8.15 - Logging & A.8.24 - Use of cryptography
7
+ Requirement: Audit logs must be encrypted at rest
8
+
9
+ Example:
10
+ >>> from complio.connectors.aws.client import AWSConnector
11
+ >>> from complio.tests_library.infrastructure.cloudtrail_encryption import CloudTrailEncryptionTest
12
+ >>>
13
+ >>> connector = AWSConnector("production", "us-east-1")
14
+ >>> connector.connect()
15
+ >>>
16
+ >>> test = CloudTrailEncryptionTest(connector)
17
+ >>> result = test.run()
18
+ >>> print(f"Passed: {result.passed}, Score: {result.score}")
19
+ """
20
+
21
+ from typing import Any, Dict
22
+
23
+ from botocore.exceptions import ClientError
24
+
25
+ from complio.connectors.aws.client import AWSConnector
26
+ from complio.tests_library.base import (
27
+ ComplianceTest,
28
+ Severity,
29
+ TestResult,
30
+ TestStatus,
31
+ )
32
+
33
+
34
+ class CloudTrailEncryptionTest(ComplianceTest):
35
+ """Test for CloudTrail log encryption compliance.
36
+
37
+ Verifies that all CloudTrail trails use KMS encryption for log files
38
+ to protect sensitive audit data at rest.
39
+
40
+ Compliance Requirements:
41
+ - All CloudTrail trails should have KmsKeyId configured
42
+ - KMS encryption provides stronger protection than S3 default encryption
43
+ - Trails without KMS encryption are flagged as medium severity
44
+
45
+ Scoring:
46
+ - 100% if all trails have KMS encryption
47
+ - Proportional score based on compliant/total ratio
48
+ - 0% if no trails have KMS encryption
49
+
50
+ Example:
51
+ >>> test = CloudTrailEncryptionTest(connector)
52
+ >>> result = test.execute()
53
+ >>> for finding in result.findings:
54
+ ... print(f"{finding.resource_id}: {finding.title}")
55
+ """
56
+
57
+ def __init__(self, connector: AWSConnector) -> None:
58
+ """Initialize CloudTrail encryption test.
59
+
60
+ Args:
61
+ connector: AWS connector instance
62
+ """
63
+ super().__init__(
64
+ test_id="cloudtrail_encryption",
65
+ test_name="CloudTrail Log Encryption Check",
66
+ description="Verify all CloudTrail trails use KMS encryption for log files",
67
+ control_id="A.8.15",
68
+ connector=connector,
69
+ scope="regional",
70
+ )
71
+
72
+ def execute(self) -> TestResult:
73
+ """Execute CloudTrail encryption compliance test.
74
+
75
+ Returns:
76
+ TestResult with findings for trails without KMS encryption
77
+
78
+ Example:
79
+ >>> test = CloudTrailEncryptionTest(connector)
80
+ >>> result = test.execute()
81
+ >>> print(result.score)
82
+ 75.0
83
+ """
84
+ result = TestResult(
85
+ test_id=self.test_id,
86
+ test_name=self.test_name,
87
+ status=TestStatus.PASSED,
88
+ passed=True,
89
+ score=100.0,
90
+ )
91
+
92
+ try:
93
+ # Get CloudTrail client
94
+ cloudtrail_client = self.connector.get_client("cloudtrail")
95
+
96
+ # List all trails
97
+ self.logger.info("listing_cloudtrail_trails")
98
+ response = cloudtrail_client.describe_trails()
99
+ trails = response.get("trailList", [])
100
+
101
+ if not trails:
102
+ self.logger.info("no_cloudtrail_trails_found")
103
+ result.metadata["message"] = "No CloudTrail trails found in region"
104
+ result.score = 0.0
105
+ result.passed = False
106
+ result.status = TestStatus.FAILED
107
+
108
+ # Create finding for missing trails
109
+ finding = self.create_finding(
110
+ resource_id="cloudtrail",
111
+ resource_type="cloudtrail",
112
+ severity=Severity.CRITICAL,
113
+ title="No CloudTrail trails configured",
114
+ description="No CloudTrail trails are configured in this region. "
115
+ "CloudTrail is required for audit logging and compliance.",
116
+ remediation=(
117
+ "Create a CloudTrail trail with KMS encryption:\n"
118
+ "1. Create a KMS key for CloudTrail:\n"
119
+ " aws kms create-key --description 'CloudTrail encryption key'\n"
120
+ "2. Create the trail:\n"
121
+ " aws cloudtrail create-trail --name my-trail \\\n"
122
+ " --s3-bucket-name my-cloudtrail-bucket \\\n"
123
+ " --kms-key-id <key-id>\n"
124
+ "3. Start logging:\n"
125
+ " aws cloudtrail start-logging --name my-trail"
126
+ ),
127
+ evidence=self.create_evidence(
128
+ resource_id="cloudtrail",
129
+ resource_type="cloudtrail",
130
+ data={"trails_count": 0}
131
+ )
132
+ )
133
+ result.add_finding(finding)
134
+
135
+ return result
136
+
137
+ self.logger.info("cloudtrail_trails_found", count=len(trails))
138
+
139
+ # Check each trail for KMS encryption
140
+ encrypted_count = 0
141
+ total_count = len(trails)
142
+
143
+ for trail in trails:
144
+ trail_name = trail.get("Name")
145
+ trail_arn = trail.get("TrailARN")
146
+ kms_key_id = trail.get("KmsKeyId")
147
+ result.resources_scanned += 1
148
+
149
+ # Get additional trail details
150
+ is_multiregion = trail.get("IsMultiRegionTrail", False)
151
+ is_organization = trail.get("IsOrganizationTrail", False)
152
+ s3_bucket = trail.get("S3BucketName", "unknown")
153
+ is_logging = trail.get("IsLogging", False)
154
+
155
+ # Check if KMS encryption is enabled
156
+ has_kms_encryption = kms_key_id is not None and kms_key_id != ""
157
+
158
+ # Create evidence
159
+ evidence = self.create_evidence(
160
+ resource_id=trail_name,
161
+ resource_type="cloudtrail_trail",
162
+ data={
163
+ "trail_name": trail_name,
164
+ "trail_arn": trail_arn,
165
+ "kms_key_id": kms_key_id,
166
+ "has_kms_encryption": has_kms_encryption,
167
+ "is_multiregion_trail": is_multiregion,
168
+ "is_organization_trail": is_organization,
169
+ "s3_bucket_name": s3_bucket,
170
+ "is_logging": is_logging,
171
+ "home_region": trail.get("HomeRegion"),
172
+ }
173
+ )
174
+ result.add_evidence(evidence)
175
+
176
+ if has_kms_encryption:
177
+ encrypted_count += 1
178
+ self.logger.debug(
179
+ "trail_kms_encrypted",
180
+ trail_name=trail_name,
181
+ kms_key_id=kms_key_id
182
+ )
183
+ else:
184
+ # Create finding for trail without KMS encryption
185
+ finding = self.create_finding(
186
+ resource_id=trail_name,
187
+ resource_type="cloudtrail_trail",
188
+ severity=Severity.MEDIUM,
189
+ title="CloudTrail log encryption with KMS not enabled",
190
+ description=f"CloudTrail trail '{trail_name}' does not use KMS encryption for log files. "
191
+ "While S3 bucket encryption may be enabled, KMS provides stronger key management "
192
+ "and audit capabilities. ISO 27001 A.8.15 and A.8.24 recommend encryption with "
193
+ "customer-controlled keys.",
194
+ remediation=(
195
+ f"Enable KMS encryption for trail '{trail_name}':\n"
196
+ "1. Create a KMS key (if not already created):\n"
197
+ " aws kms create-key --description 'CloudTrail encryption key'\n"
198
+ "2. Update the trail to use KMS encryption:\n"
199
+ f" aws cloudtrail update-trail --name {trail_name} \\\n"
200
+ " --kms-key-id <key-arn>\n\n"
201
+ "Or use AWS Console:\n"
202
+ "1. Go to CloudTrail → Trails\n"
203
+ f"2. Select trail '{trail_name}'\n"
204
+ "3. Click Edit\n"
205
+ "4. Under 'Log file SSE-KMS encryption', enable encryption\n"
206
+ "5. Select or create a KMS key\n"
207
+ "6. Click Save changes\n\n"
208
+ "Note: Ensure CloudTrail service has permission to use the KMS key."
209
+ ),
210
+ evidence=evidence
211
+ )
212
+ result.add_finding(finding)
213
+
214
+ self.logger.warning(
215
+ "trail_not_kms_encrypted",
216
+ trail_name=trail_name
217
+ )
218
+
219
+ # Calculate compliance score
220
+ if total_count > 0:
221
+ result.score = (encrypted_count / total_count) * 100
222
+
223
+ # Determine pass/fail
224
+ result.passed = encrypted_count == total_count
225
+ result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
226
+
227
+ # Add metadata
228
+ result.metadata = {
229
+ "total_trails": total_count,
230
+ "trails_with_kms_encryption": encrypted_count,
231
+ "trails_without_kms_encryption": total_count - encrypted_count,
232
+ "compliance_percentage": result.score,
233
+ "region": self.connector.region,
234
+ }
235
+
236
+ self.logger.info(
237
+ "cloudtrail_encryption_test_completed",
238
+ total=total_count,
239
+ encrypted=encrypted_count,
240
+ score=result.score,
241
+ passed=result.passed
242
+ )
243
+
244
+ except ClientError as e:
245
+ error_code = e.response.get("Error", {}).get("Code")
246
+ self.logger.error("cloudtrail_encryption_test_error", error_code=error_code, error=str(e))
247
+ result.status = TestStatus.ERROR
248
+ result.passed = False
249
+ result.score = 0.0
250
+ result.error_message = f"AWS API Error: {error_code} - {str(e)}"
251
+
252
+ except Exception as e:
253
+ self.logger.error("cloudtrail_encryption_test_error", error=str(e))
254
+ result.status = TestStatus.ERROR
255
+ result.passed = False
256
+ result.score = 0.0
257
+ result.error_message = str(e)
258
+
259
+ return result
260
+
261
+
262
+ # ============================================================================
263
+ # CONVENIENCE FUNCTION
264
+ # ============================================================================
265
+
266
+
267
+ def run_cloudtrail_encryption_test(connector: AWSConnector) -> TestResult:
268
+ """Run CloudTrail encryption compliance test.
269
+
270
+ Convenience function for running the test.
271
+
272
+ Args:
273
+ connector: AWS connector
274
+
275
+ Returns:
276
+ TestResult
277
+
278
+ Example:
279
+ >>> from complio.connectors.aws.client import AWSConnector
280
+ >>> connector = AWSConnector("production", "us-east-1")
281
+ >>> connector.connect()
282
+ >>> result = run_cloudtrail_encryption_test(connector)
283
+ >>> print(f"Score: {result.score}%")
284
+ """
285
+ test = CloudTrailEncryptionTest(connector)
286
+ return test.execute()
@@ -0,0 +1,274 @@
1
+ """
2
+ CloudTrail log file validation compliance test.
3
+
4
+ Checks that all CloudTrail trails have log file validation enabled.
5
+
6
+ ISO 27001 Control: A.8.15 - Logging
7
+ Requirement: Log integrity must be ensured through validation mechanisms
8
+
9
+ Example:
10
+ >>> from complio.connectors.aws.client import AWSConnector
11
+ >>> from complio.tests_library.infrastructure.cloudtrail_log_validation import CloudTrailLogValidationTest
12
+ >>>
13
+ >>> connector = AWSConnector("production", "us-east-1")
14
+ >>> connector.connect()
15
+ >>>
16
+ >>> test = CloudTrailLogValidationTest(connector)
17
+ >>> result = test.run()
18
+ >>> print(f"Passed: {result.passed}, Score: {result.score}")
19
+ """
20
+
21
+ from typing import Any, Dict
22
+
23
+ from botocore.exceptions import ClientError
24
+
25
+ from complio.connectors.aws.client import AWSConnector
26
+ from complio.tests_library.base import (
27
+ ComplianceTest,
28
+ Severity,
29
+ TestResult,
30
+ TestStatus,
31
+ )
32
+
33
+
34
+ class CloudTrailLogValidationTest(ComplianceTest):
35
+ """Test for CloudTrail log file validation compliance.
36
+
37
+ Verifies that all CloudTrail trails have log file validation enabled
38
+ to ensure log integrity and detect tampering.
39
+
40
+ Compliance Requirements:
41
+ - All CloudTrail trails must have LogFileValidationEnabled=True
42
+ - Log file validation provides cryptographic proof of log integrity
43
+ - Trails without validation are non-compliant
44
+
45
+ Scoring:
46
+ - 100% if all trails have validation enabled
47
+ - Proportional score based on compliant/total ratio
48
+ - 0% if no trails have validation enabled
49
+
50
+ Example:
51
+ >>> test = CloudTrailLogValidationTest(connector)
52
+ >>> result = test.execute()
53
+ >>> for finding in result.findings:
54
+ ... print(f"{finding.resource_id}: {finding.title}")
55
+ """
56
+
57
+ def __init__(self, connector: AWSConnector) -> None:
58
+ """Initialize CloudTrail log validation test.
59
+
60
+ Args:
61
+ connector: AWS connector instance
62
+ """
63
+ super().__init__(
64
+ test_id="cloudtrail_log_validation",
65
+ test_name="CloudTrail Log File Validation Check",
66
+ description="Verify all CloudTrail trails have log file validation enabled",
67
+ control_id="A.8.15",
68
+ connector=connector,
69
+ scope="regional",
70
+ )
71
+
72
+ def execute(self) -> TestResult:
73
+ """Execute CloudTrail log validation compliance test.
74
+
75
+ Returns:
76
+ TestResult with findings for trails without validation
77
+
78
+ Example:
79
+ >>> test = CloudTrailLogValidationTest(connector)
80
+ >>> result = test.execute()
81
+ >>> print(result.score)
82
+ 100.0
83
+ """
84
+ result = TestResult(
85
+ test_id=self.test_id,
86
+ test_name=self.test_name,
87
+ status=TestStatus.PASSED,
88
+ passed=True,
89
+ score=100.0,
90
+ )
91
+
92
+ try:
93
+ # Get CloudTrail client
94
+ cloudtrail_client = self.connector.get_client("cloudtrail")
95
+
96
+ # List all trails
97
+ self.logger.info("listing_cloudtrail_trails")
98
+ response = cloudtrail_client.describe_trails()
99
+ trails = response.get("trailList", [])
100
+
101
+ if not trails:
102
+ self.logger.info("no_cloudtrail_trails_found")
103
+ result.metadata["message"] = "No CloudTrail trails found in region"
104
+ result.score = 0.0
105
+ result.passed = False
106
+ result.status = TestStatus.FAILED
107
+
108
+ # Create finding for missing trails
109
+ finding = self.create_finding(
110
+ resource_id="cloudtrail",
111
+ resource_type="cloudtrail",
112
+ severity=Severity.CRITICAL,
113
+ title="No CloudTrail trails configured",
114
+ description="No CloudTrail trails are configured in this region. "
115
+ "CloudTrail is required for audit logging and compliance.",
116
+ remediation=(
117
+ "Create a CloudTrail trail:\n"
118
+ "1. Go to AWS Console → CloudTrail → Create trail\n"
119
+ "2. Enable log file validation\n"
120
+ "3. Configure S3 bucket for log storage\n"
121
+ "Or use AWS CLI:\n"
122
+ "aws cloudtrail create-trail --name my-trail \\\n"
123
+ " --s3-bucket-name my-cloudtrail-bucket \\\n"
124
+ " --enable-log-file-validation"
125
+ ),
126
+ evidence=self.create_evidence(
127
+ resource_id="cloudtrail",
128
+ resource_type="cloudtrail",
129
+ data={"trails_count": 0}
130
+ )
131
+ )
132
+ result.add_finding(finding)
133
+
134
+ return result
135
+
136
+ self.logger.info("cloudtrail_trails_found", count=len(trails))
137
+
138
+ # Check each trail for log file validation
139
+ compliant_count = 0
140
+ total_count = len(trails)
141
+
142
+ for trail in trails:
143
+ trail_name = trail.get("Name")
144
+ trail_arn = trail.get("TrailARN")
145
+ log_validation_enabled = trail.get("LogFileValidationEnabled", False)
146
+ result.resources_scanned += 1
147
+
148
+ # Get additional trail details
149
+ is_multiregion = trail.get("IsMultiRegionTrail", False)
150
+ is_organization = trail.get("IsOrganizationTrail", False)
151
+ s3_bucket = trail.get("S3BucketName", "unknown")
152
+
153
+ # Create evidence
154
+ evidence = self.create_evidence(
155
+ resource_id=trail_name,
156
+ resource_type="cloudtrail_trail",
157
+ data={
158
+ "trail_name": trail_name,
159
+ "trail_arn": trail_arn,
160
+ "log_file_validation_enabled": log_validation_enabled,
161
+ "is_multiregion_trail": is_multiregion,
162
+ "is_organization_trail": is_organization,
163
+ "s3_bucket_name": s3_bucket,
164
+ "home_region": trail.get("HomeRegion"),
165
+ }
166
+ )
167
+ result.add_evidence(evidence)
168
+
169
+ if log_validation_enabled:
170
+ compliant_count += 1
171
+ self.logger.debug(
172
+ "trail_validation_enabled",
173
+ trail_name=trail_name,
174
+ is_multiregion=is_multiregion
175
+ )
176
+ else:
177
+ # Create finding for trail without validation
178
+ finding = self.create_finding(
179
+ resource_id=trail_name,
180
+ resource_type="cloudtrail_trail",
181
+ severity=Severity.MEDIUM,
182
+ title="CloudTrail log file validation not enabled",
183
+ description=f"CloudTrail trail '{trail_name}' does not have log file validation enabled. "
184
+ "Without validation, log files can be tampered with or deleted without detection. "
185
+ "This violates ISO 27001 A.8.15 requirement for log integrity.",
186
+ remediation=(
187
+ f"Enable log file validation for trail '{trail_name}':\n"
188
+ f"aws cloudtrail update-trail --name {trail_name} \\\n"
189
+ " --enable-log-file-validation\n\n"
190
+ "Or use AWS Console:\n"
191
+ "1. Go to CloudTrail → Trails\n"
192
+ f"2. Select trail '{trail_name}'\n"
193
+ "3. Click Edit\n"
194
+ "4. Under Additional settings, enable 'Log file validation'\n"
195
+ "5. Click Save changes\n\n"
196
+ "Note: Validation only applies to logs created after enabling."
197
+ ),
198
+ evidence=evidence
199
+ )
200
+ result.add_finding(finding)
201
+
202
+ self.logger.warning(
203
+ "trail_validation_disabled",
204
+ trail_name=trail_name
205
+ )
206
+
207
+ # Calculate compliance score
208
+ if total_count > 0:
209
+ result.score = (compliant_count / total_count) * 100
210
+
211
+ # Determine pass/fail
212
+ result.passed = compliant_count == total_count
213
+ result.status = TestStatus.PASSED if result.passed else TestStatus.FAILED
214
+
215
+ # Add metadata
216
+ result.metadata = {
217
+ "total_trails": total_count,
218
+ "trails_with_validation": compliant_count,
219
+ "trails_without_validation": total_count - compliant_count,
220
+ "compliance_percentage": result.score,
221
+ "region": self.connector.region,
222
+ }
223
+
224
+ self.logger.info(
225
+ "cloudtrail_log_validation_test_completed",
226
+ total=total_count,
227
+ compliant=compliant_count,
228
+ score=result.score,
229
+ passed=result.passed
230
+ )
231
+
232
+ except ClientError as e:
233
+ error_code = e.response.get("Error", {}).get("Code")
234
+ self.logger.error("cloudtrail_validation_test_error", error_code=error_code, error=str(e))
235
+ result.status = TestStatus.ERROR
236
+ result.passed = False
237
+ result.score = 0.0
238
+ result.error_message = f"AWS API Error: {error_code} - {str(e)}"
239
+
240
+ except Exception as e:
241
+ self.logger.error("cloudtrail_validation_test_error", error=str(e))
242
+ result.status = TestStatus.ERROR
243
+ result.passed = False
244
+ result.score = 0.0
245
+ result.error_message = str(e)
246
+
247
+ return result
248
+
249
+
250
+ # ============================================================================
251
+ # CONVENIENCE FUNCTION
252
+ # ============================================================================
253
+
254
+
255
+ def run_cloudtrail_log_validation_test(connector: AWSConnector) -> TestResult:
256
+ """Run CloudTrail log validation compliance test.
257
+
258
+ Convenience function for running the test.
259
+
260
+ Args:
261
+ connector: AWS connector
262
+
263
+ Returns:
264
+ TestResult
265
+
266
+ Example:
267
+ >>> from complio.connectors.aws.client import AWSConnector
268
+ >>> connector = AWSConnector("production", "us-east-1")
269
+ >>> connector.connect()
270
+ >>> result = run_cloudtrail_log_validation_test(connector)
271
+ >>> print(f"Score: {result.score}%")
272
+ """
273
+ test = CloudTrailLogValidationTest(connector)
274
+ return test.execute()