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.
- CHANGELOG.md +208 -0
- README.md +343 -0
- complio/__init__.py +48 -0
- complio/cli/__init__.py +0 -0
- complio/cli/banner.py +87 -0
- complio/cli/commands/__init__.py +0 -0
- complio/cli/commands/history.py +439 -0
- complio/cli/commands/scan.py +700 -0
- complio/cli/main.py +115 -0
- complio/cli/output.py +338 -0
- complio/config/__init__.py +17 -0
- complio/config/settings.py +333 -0
- complio/connectors/__init__.py +9 -0
- complio/connectors/aws/__init__.py +0 -0
- complio/connectors/aws/client.py +342 -0
- complio/connectors/base.py +135 -0
- complio/core/__init__.py +10 -0
- complio/core/registry.py +228 -0
- complio/core/runner.py +351 -0
- complio/py.typed +0 -0
- complio/reporters/__init__.py +7 -0
- complio/reporters/generator.py +417 -0
- complio/tests_library/__init__.py +0 -0
- complio/tests_library/base.py +492 -0
- complio/tests_library/identity/__init__.py +0 -0
- complio/tests_library/identity/access_key_rotation.py +302 -0
- complio/tests_library/identity/mfa_enforcement.py +327 -0
- complio/tests_library/identity/root_account_protection.py +470 -0
- complio/tests_library/infrastructure/__init__.py +0 -0
- complio/tests_library/infrastructure/cloudtrail_encryption.py +286 -0
- complio/tests_library/infrastructure/cloudtrail_log_validation.py +274 -0
- complio/tests_library/infrastructure/cloudtrail_logging.py +400 -0
- complio/tests_library/infrastructure/ebs_encryption.py +244 -0
- complio/tests_library/infrastructure/ec2_security_groups.py +321 -0
- complio/tests_library/infrastructure/iam_password_policy.py +460 -0
- complio/tests_library/infrastructure/nacl_security.py +356 -0
- complio/tests_library/infrastructure/rds_encryption.py +252 -0
- complio/tests_library/infrastructure/s3_encryption.py +301 -0
- complio/tests_library/infrastructure/s3_public_access.py +369 -0
- complio/tests_library/infrastructure/secrets_manager_encryption.py +248 -0
- complio/tests_library/infrastructure/vpc_flow_logs.py +287 -0
- complio/tests_library/logging/__init__.py +0 -0
- complio/tests_library/logging/cloudwatch_alarms.py +354 -0
- complio/tests_library/logging/cloudwatch_logs_encryption.py +281 -0
- complio/tests_library/logging/cloudwatch_retention.py +252 -0
- complio/tests_library/logging/config_enabled.py +393 -0
- complio/tests_library/logging/eventbridge_rules.py +460 -0
- complio/tests_library/logging/guardduty_enabled.py +436 -0
- complio/tests_library/logging/security_hub_enabled.py +416 -0
- complio/tests_library/logging/sns_encryption.py +273 -0
- complio/tests_library/network/__init__.py +0 -0
- complio/tests_library/network/alb_nlb_security.py +421 -0
- complio/tests_library/network/api_gateway_security.py +452 -0
- complio/tests_library/network/cloudfront_https.py +332 -0
- complio/tests_library/network/direct_connect_security.py +343 -0
- complio/tests_library/network/nacl_configuration.py +367 -0
- complio/tests_library/network/network_firewall.py +355 -0
- complio/tests_library/network/transit_gateway_security.py +318 -0
- complio/tests_library/network/vpc_endpoints_security.py +339 -0
- complio/tests_library/network/vpn_security.py +333 -0
- complio/tests_library/network/waf_configuration.py +428 -0
- complio/tests_library/security/__init__.py +0 -0
- complio/tests_library/security/kms_key_rotation.py +314 -0
- complio/tests_library/storage/__init__.py +0 -0
- complio/tests_library/storage/backup_encryption.py +288 -0
- complio/tests_library/storage/dynamodb_encryption.py +280 -0
- complio/tests_library/storage/efs_encryption.py +257 -0
- complio/tests_library/storage/elasticache_encryption.py +370 -0
- complio/tests_library/storage/redshift_encryption.py +252 -0
- complio/tests_library/storage/s3_versioning.py +264 -0
- complio/utils/__init__.py +26 -0
- complio/utils/errors.py +179 -0
- complio/utils/exceptions.py +151 -0
- complio/utils/history.py +243 -0
- complio/utils/logger.py +391 -0
- complio-0.1.1.dist-info/METADATA +385 -0
- complio-0.1.1.dist-info/RECORD +79 -0
- complio-0.1.1.dist-info/WHEEL +4 -0
- 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()
|