aws-cis-controls-assessment 1.0.3__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.
- aws_cis_assessment/__init__.py +11 -0
- aws_cis_assessment/cli/__init__.py +3 -0
- aws_cis_assessment/cli/examples.py +274 -0
- aws_cis_assessment/cli/main.py +1259 -0
- aws_cis_assessment/cli/utils.py +356 -0
- aws_cis_assessment/config/__init__.py +1 -0
- aws_cis_assessment/config/config_loader.py +328 -0
- aws_cis_assessment/config/rules/cis_controls_ig1.yaml +590 -0
- aws_cis_assessment/config/rules/cis_controls_ig2.yaml +412 -0
- aws_cis_assessment/config/rules/cis_controls_ig3.yaml +100 -0
- aws_cis_assessment/controls/__init__.py +1 -0
- aws_cis_assessment/controls/base_control.py +400 -0
- aws_cis_assessment/controls/ig1/__init__.py +239 -0
- aws_cis_assessment/controls/ig1/control_1_1.py +586 -0
- aws_cis_assessment/controls/ig1/control_2_2.py +231 -0
- aws_cis_assessment/controls/ig1/control_3_3.py +718 -0
- aws_cis_assessment/controls/ig1/control_3_4.py +235 -0
- aws_cis_assessment/controls/ig1/control_4_1.py +461 -0
- aws_cis_assessment/controls/ig1/control_access_keys.py +310 -0
- aws_cis_assessment/controls/ig1/control_advanced_security.py +512 -0
- aws_cis_assessment/controls/ig1/control_backup_recovery.py +510 -0
- aws_cis_assessment/controls/ig1/control_cloudtrail_logging.py +197 -0
- aws_cis_assessment/controls/ig1/control_critical_security.py +422 -0
- aws_cis_assessment/controls/ig1/control_data_protection.py +898 -0
- aws_cis_assessment/controls/ig1/control_iam_advanced.py +573 -0
- aws_cis_assessment/controls/ig1/control_iam_governance.py +493 -0
- aws_cis_assessment/controls/ig1/control_iam_policies.py +383 -0
- aws_cis_assessment/controls/ig1/control_instance_optimization.py +100 -0
- aws_cis_assessment/controls/ig1/control_network_enhancements.py +203 -0
- aws_cis_assessment/controls/ig1/control_network_security.py +672 -0
- aws_cis_assessment/controls/ig1/control_s3_enhancements.py +173 -0
- aws_cis_assessment/controls/ig1/control_s3_security.py +422 -0
- aws_cis_assessment/controls/ig1/control_vpc_security.py +235 -0
- aws_cis_assessment/controls/ig2/__init__.py +172 -0
- aws_cis_assessment/controls/ig2/control_3_10.py +698 -0
- aws_cis_assessment/controls/ig2/control_3_11.py +1330 -0
- aws_cis_assessment/controls/ig2/control_5_2.py +393 -0
- aws_cis_assessment/controls/ig2/control_advanced_encryption.py +355 -0
- aws_cis_assessment/controls/ig2/control_codebuild_security.py +263 -0
- aws_cis_assessment/controls/ig2/control_encryption_rest.py +382 -0
- aws_cis_assessment/controls/ig2/control_encryption_transit.py +382 -0
- aws_cis_assessment/controls/ig2/control_network_ha.py +467 -0
- aws_cis_assessment/controls/ig2/control_remaining_encryption.py +426 -0
- aws_cis_assessment/controls/ig2/control_remaining_rules.py +363 -0
- aws_cis_assessment/controls/ig2/control_service_logging.py +402 -0
- aws_cis_assessment/controls/ig3/__init__.py +49 -0
- aws_cis_assessment/controls/ig3/control_12_8.py +395 -0
- aws_cis_assessment/controls/ig3/control_13_1.py +467 -0
- aws_cis_assessment/controls/ig3/control_3_14.py +523 -0
- aws_cis_assessment/controls/ig3/control_7_1.py +359 -0
- aws_cis_assessment/core/__init__.py +1 -0
- aws_cis_assessment/core/accuracy_validator.py +425 -0
- aws_cis_assessment/core/assessment_engine.py +1266 -0
- aws_cis_assessment/core/audit_trail.py +491 -0
- aws_cis_assessment/core/aws_client_factory.py +313 -0
- aws_cis_assessment/core/error_handler.py +607 -0
- aws_cis_assessment/core/models.py +166 -0
- aws_cis_assessment/core/scoring_engine.py +459 -0
- aws_cis_assessment/reporters/__init__.py +8 -0
- aws_cis_assessment/reporters/base_reporter.py +454 -0
- aws_cis_assessment/reporters/csv_reporter.py +835 -0
- aws_cis_assessment/reporters/html_reporter.py +2162 -0
- aws_cis_assessment/reporters/json_reporter.py +561 -0
- aws_cis_controls_assessment-1.0.3.dist-info/METADATA +248 -0
- aws_cis_controls_assessment-1.0.3.dist-info/RECORD +77 -0
- aws_cis_controls_assessment-1.0.3.dist-info/WHEEL +5 -0
- aws_cis_controls_assessment-1.0.3.dist-info/entry_points.txt +2 -0
- aws_cis_controls_assessment-1.0.3.dist-info/licenses/LICENSE +21 -0
- aws_cis_controls_assessment-1.0.3.dist-info/top_level.txt +2 -0
- docs/README.md +94 -0
- docs/assessment-logic.md +766 -0
- docs/cli-reference.md +698 -0
- docs/config-rule-mappings.md +393 -0
- docs/developer-guide.md +858 -0
- docs/installation.md +299 -0
- docs/troubleshooting.md +634 -0
- docs/user-guide.md +487 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""Remaining AWS Config Rules - Final implementation."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Any
|
|
4
|
+
import logging
|
|
5
|
+
from botocore.exceptions import ClientError
|
|
6
|
+
|
|
7
|
+
from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
|
|
8
|
+
from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
|
|
9
|
+
from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ACMCertificateExpirationCheckAssessment(BaseConfigRuleAssessment):
|
|
15
|
+
"""Assessment for acm-certificate-expiration-check AWS Config rule."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__(
|
|
19
|
+
rule_name="acm-certificate-expiration-check",
|
|
20
|
+
control_id="4.1",
|
|
21
|
+
resource_types=["AWS::ACM::Certificate"]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
25
|
+
"""Get ACM certificates."""
|
|
26
|
+
if resource_type != "AWS::ACM::Certificate":
|
|
27
|
+
return []
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
acm_client = aws_factory.get_client('acm', region)
|
|
31
|
+
|
|
32
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
33
|
+
lambda: acm_client.list_certificates()
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
certificates = []
|
|
37
|
+
for cert in response.get('CertificateSummaryList', []):
|
|
38
|
+
certificates.append({
|
|
39
|
+
'CertificateArn': cert.get('CertificateArn'),
|
|
40
|
+
'DomainName': cert.get('DomainName')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
return certificates
|
|
44
|
+
|
|
45
|
+
except ClientError as e:
|
|
46
|
+
logger.error(f"Error retrieving ACM certificates in region {region}: {e}")
|
|
47
|
+
raise
|
|
48
|
+
|
|
49
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
50
|
+
"""Evaluate if ACM certificate is not expired or expiring soon."""
|
|
51
|
+
cert_arn = resource.get('CertificateArn', 'unknown')
|
|
52
|
+
domain_name = resource.get('DomainName', 'unknown')
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
acm_client = aws_factory.get_client('acm', region)
|
|
56
|
+
|
|
57
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
58
|
+
lambda: acm_client.describe_certificate(CertificateArn=cert_arn)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
certificate = response.get('Certificate', {})
|
|
62
|
+
not_after = certificate.get('NotAfter')
|
|
63
|
+
status = certificate.get('Status')
|
|
64
|
+
|
|
65
|
+
if status == 'EXPIRED':
|
|
66
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
67
|
+
evaluation_reason = f"ACM certificate {domain_name} is expired"
|
|
68
|
+
elif not_after:
|
|
69
|
+
from datetime import datetime, timezone, timedelta
|
|
70
|
+
|
|
71
|
+
# Check if certificate expires within 30 days
|
|
72
|
+
expiry_date = not_after
|
|
73
|
+
if expiry_date.tzinfo is None:
|
|
74
|
+
expiry_date = expiry_date.replace(tzinfo=timezone.utc)
|
|
75
|
+
|
|
76
|
+
now = datetime.now(timezone.utc)
|
|
77
|
+
days_until_expiry = (expiry_date - now).days
|
|
78
|
+
|
|
79
|
+
if days_until_expiry <= 30:
|
|
80
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
81
|
+
evaluation_reason = f"ACM certificate {domain_name} expires in {days_until_expiry} days"
|
|
82
|
+
else:
|
|
83
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
84
|
+
evaluation_reason = f"ACM certificate {domain_name} expires in {days_until_expiry} days"
|
|
85
|
+
else:
|
|
86
|
+
compliance_status = ComplianceStatus.ERROR
|
|
87
|
+
evaluation_reason = f"Could not determine expiry date for certificate {domain_name}"
|
|
88
|
+
|
|
89
|
+
except ClientError as e:
|
|
90
|
+
compliance_status = ComplianceStatus.ERROR
|
|
91
|
+
evaluation_reason = f"Error checking certificate expiration for {domain_name}: {str(e)}"
|
|
92
|
+
|
|
93
|
+
return ComplianceResult(
|
|
94
|
+
resource_id=domain_name,
|
|
95
|
+
resource_type="AWS::ACM::Certificate",
|
|
96
|
+
compliance_status=compliance_status,
|
|
97
|
+
evaluation_reason=evaluation_reason,
|
|
98
|
+
config_rule_name=self.rule_name,
|
|
99
|
+
region=region
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class DynamoDBAutoScalingEnabledAssessment(BaseConfigRuleAssessment):
|
|
104
|
+
"""Assessment for dynamodb-autoscaling-enabled AWS Config rule."""
|
|
105
|
+
|
|
106
|
+
def __init__(self):
|
|
107
|
+
super().__init__(
|
|
108
|
+
rule_name="dynamodb-autoscaling-enabled",
|
|
109
|
+
control_id="12.2",
|
|
110
|
+
resource_types=["AWS::DynamoDB::Table"]
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
114
|
+
"""Get DynamoDB tables."""
|
|
115
|
+
if resource_type != "AWS::DynamoDB::Table":
|
|
116
|
+
return []
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
dynamodb_client = aws_factory.get_client('dynamodb', region)
|
|
120
|
+
|
|
121
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
122
|
+
lambda: dynamodb_client.list_tables()
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
tables = []
|
|
126
|
+
for table_name in response.get('TableNames', []):
|
|
127
|
+
tables.append({
|
|
128
|
+
'TableName': table_name
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
return tables
|
|
132
|
+
|
|
133
|
+
except ClientError as e:
|
|
134
|
+
logger.error(f"Error retrieving DynamoDB tables in region {region}: {e}")
|
|
135
|
+
raise
|
|
136
|
+
|
|
137
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
138
|
+
"""Evaluate if DynamoDB table has auto scaling enabled."""
|
|
139
|
+
table_name = resource.get('TableName', 'unknown')
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
# Check if table has auto scaling targets
|
|
143
|
+
autoscaling_client = aws_factory.get_client('application-autoscaling', region)
|
|
144
|
+
|
|
145
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
146
|
+
lambda: autoscaling_client.describe_scalable_targets(
|
|
147
|
+
ServiceNamespace='dynamodb',
|
|
148
|
+
ResourceIds=[f'table/{table_name}']
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
scalable_targets = response.get('ScalableTargets', [])
|
|
153
|
+
|
|
154
|
+
if scalable_targets:
|
|
155
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
156
|
+
evaluation_reason = f"DynamoDB table {table_name} has auto scaling enabled"
|
|
157
|
+
else:
|
|
158
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
159
|
+
evaluation_reason = f"DynamoDB table {table_name} does not have auto scaling enabled"
|
|
160
|
+
|
|
161
|
+
except ClientError as e:
|
|
162
|
+
compliance_status = ComplianceStatus.ERROR
|
|
163
|
+
evaluation_reason = f"Error checking auto scaling for table {table_name}: {str(e)}"
|
|
164
|
+
|
|
165
|
+
return ComplianceResult(
|
|
166
|
+
resource_id=table_name,
|
|
167
|
+
resource_type="AWS::DynamoDB::Table",
|
|
168
|
+
compliance_status=compliance_status,
|
|
169
|
+
evaluation_reason=evaluation_reason,
|
|
170
|
+
config_rule_name=self.rule_name,
|
|
171
|
+
region=region
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class RedshiftEnhancedVPCRoutingEnabledAssessment(BaseConfigRuleAssessment):
|
|
176
|
+
"""Assessment for redshift-enhanced-vpc-routing-enabled AWS Config rule."""
|
|
177
|
+
|
|
178
|
+
def __init__(self):
|
|
179
|
+
super().__init__(
|
|
180
|
+
rule_name="redshift-enhanced-vpc-routing-enabled",
|
|
181
|
+
control_id="3.3",
|
|
182
|
+
resource_types=["AWS::Redshift::Cluster"]
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
186
|
+
"""Get Redshift clusters."""
|
|
187
|
+
if resource_type != "AWS::Redshift::Cluster":
|
|
188
|
+
return []
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
redshift_client = aws_factory.get_client('redshift', region)
|
|
192
|
+
|
|
193
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
194
|
+
lambda: redshift_client.describe_clusters()
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
clusters = []
|
|
198
|
+
for cluster in response.get('Clusters', []):
|
|
199
|
+
clusters.append({
|
|
200
|
+
'ClusterIdentifier': cluster.get('ClusterIdentifier'),
|
|
201
|
+
'EnhancedVpcRouting': cluster.get('EnhancedVpcRouting', False)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
return clusters
|
|
205
|
+
|
|
206
|
+
except ClientError as e:
|
|
207
|
+
logger.error(f"Error retrieving Redshift clusters in region {region}: {e}")
|
|
208
|
+
raise
|
|
209
|
+
|
|
210
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
211
|
+
"""Evaluate if Redshift cluster has enhanced VPC routing enabled."""
|
|
212
|
+
cluster_id = resource.get('ClusterIdentifier', 'unknown')
|
|
213
|
+
enhanced_vpc_routing = resource.get('EnhancedVpcRouting', False)
|
|
214
|
+
|
|
215
|
+
if enhanced_vpc_routing:
|
|
216
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
217
|
+
evaluation_reason = f"Redshift cluster {cluster_id} has enhanced VPC routing enabled"
|
|
218
|
+
else:
|
|
219
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
220
|
+
evaluation_reason = f"Redshift cluster {cluster_id} does not have enhanced VPC routing enabled"
|
|
221
|
+
|
|
222
|
+
return ComplianceResult(
|
|
223
|
+
resource_id=cluster_id,
|
|
224
|
+
resource_type="AWS::Redshift::Cluster",
|
|
225
|
+
compliance_status=compliance_status,
|
|
226
|
+
evaluation_reason=evaluation_reason,
|
|
227
|
+
config_rule_name=self.rule_name,
|
|
228
|
+
region=region
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class RestrictedCommonPortsAssessment(BaseConfigRuleAssessment):
|
|
233
|
+
"""Assessment for restricted-common-ports AWS Config rule."""
|
|
234
|
+
|
|
235
|
+
def __init__(self):
|
|
236
|
+
super().__init__(
|
|
237
|
+
rule_name="restricted-common-ports",
|
|
238
|
+
control_id="3.3",
|
|
239
|
+
resource_types=["AWS::EC2::SecurityGroup"]
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
243
|
+
"""Get Security Groups."""
|
|
244
|
+
if resource_type != "AWS::EC2::SecurityGroup":
|
|
245
|
+
return []
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
ec2_client = aws_factory.get_client('ec2', region)
|
|
249
|
+
|
|
250
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
251
|
+
lambda: ec2_client.describe_security_groups()
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
security_groups = []
|
|
255
|
+
for sg in response.get('SecurityGroups', []):
|
|
256
|
+
security_groups.append({
|
|
257
|
+
'GroupId': sg.get('GroupId'),
|
|
258
|
+
'GroupName': sg.get('GroupName'),
|
|
259
|
+
'IpPermissions': sg.get('IpPermissions', [])
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
return security_groups
|
|
263
|
+
|
|
264
|
+
except ClientError as e:
|
|
265
|
+
logger.error(f"Error retrieving Security Groups in region {region}: {e}")
|
|
266
|
+
raise
|
|
267
|
+
|
|
268
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
269
|
+
"""Evaluate if Security Group restricts access to common ports."""
|
|
270
|
+
group_id = resource.get('GroupId', 'unknown')
|
|
271
|
+
group_name = resource.get('GroupName', 'unknown')
|
|
272
|
+
ip_permissions = resource.get('IpPermissions', [])
|
|
273
|
+
|
|
274
|
+
# Common ports that should be restricted
|
|
275
|
+
restricted_ports = [20, 21, 22, 23, 25, 53, 80, 110, 135, 143, 443, 993, 995, 1433, 1521, 3306, 3389, 5432, 5984, 6379, 8020, 8086, 8888, 9042, 9160, 9200, 9300, 11211, 27017, 27018, 27019]
|
|
276
|
+
|
|
277
|
+
violations = []
|
|
278
|
+
|
|
279
|
+
for permission in ip_permissions:
|
|
280
|
+
from_port = permission.get('FromPort')
|
|
281
|
+
to_port = permission.get('ToPort')
|
|
282
|
+
ip_ranges = permission.get('IpRanges', [])
|
|
283
|
+
|
|
284
|
+
# Check if any restricted ports are open to 0.0.0.0/0
|
|
285
|
+
for ip_range in ip_ranges:
|
|
286
|
+
cidr = ip_range.get('CidrIp', '')
|
|
287
|
+
if cidr == '0.0.0.0/0':
|
|
288
|
+
# Check if any restricted ports are in the range
|
|
289
|
+
if from_port is not None and to_port is not None:
|
|
290
|
+
for port in restricted_ports:
|
|
291
|
+
if from_port <= port <= to_port:
|
|
292
|
+
violations.append(f"Port {port} open to 0.0.0.0/0")
|
|
293
|
+
|
|
294
|
+
if not violations:
|
|
295
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
296
|
+
evaluation_reason = f"Security Group {group_name} does not expose restricted ports to 0.0.0.0/0"
|
|
297
|
+
else:
|
|
298
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
299
|
+
evaluation_reason = f"Security Group {group_name} has violations: {'; '.join(violations[:3])}" # Limit to first 3
|
|
300
|
+
|
|
301
|
+
return ComplianceResult(
|
|
302
|
+
resource_id=group_id,
|
|
303
|
+
resource_type="AWS::EC2::SecurityGroup",
|
|
304
|
+
compliance_status=compliance_status,
|
|
305
|
+
evaluation_reason=evaluation_reason,
|
|
306
|
+
config_rule_name=self.rule_name,
|
|
307
|
+
region=region
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class AuditLogPolicyExistsAssessment(BaseConfigRuleAssessment):
|
|
312
|
+
"""Assessment for audit-log-policy-exists AWS Config rule (Process check)."""
|
|
313
|
+
|
|
314
|
+
def __init__(self):
|
|
315
|
+
super().__init__(
|
|
316
|
+
rule_name="audit-log-policy-exists (Process check)",
|
|
317
|
+
control_id="8.1",
|
|
318
|
+
resource_types=["AWS::::Account"]
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
322
|
+
"""Get account-level resource for audit log policy check."""
|
|
323
|
+
if resource_type != "AWS::::Account":
|
|
324
|
+
return []
|
|
325
|
+
|
|
326
|
+
# Return a single account resource
|
|
327
|
+
return [{'AccountId': aws_factory.account_id}]
|
|
328
|
+
|
|
329
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
330
|
+
"""Evaluate if account has audit log management policy (process check)."""
|
|
331
|
+
account_id = resource.get('AccountId', 'unknown')
|
|
332
|
+
|
|
333
|
+
# This is a process check - we can only verify if CloudTrail is configured
|
|
334
|
+
# as a proxy for audit log policy existence
|
|
335
|
+
try:
|
|
336
|
+
cloudtrail_client = aws_factory.get_client('cloudtrail', region)
|
|
337
|
+
|
|
338
|
+
response = aws_factory.aws_api_call_with_retry(
|
|
339
|
+
lambda: cloudtrail_client.describe_trails()
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
trails = response.get('trailList', [])
|
|
343
|
+
active_trails = [trail for trail in trails if trail.get('IsLogging', False)]
|
|
344
|
+
|
|
345
|
+
if active_trails:
|
|
346
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
347
|
+
evaluation_reason = f"Account {account_id} has {len(active_trails)} active CloudTrail(s) indicating audit log policy implementation"
|
|
348
|
+
else:
|
|
349
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
350
|
+
evaluation_reason = f"Account {account_id} has no active CloudTrail, indicating lack of audit log policy"
|
|
351
|
+
|
|
352
|
+
except ClientError as e:
|
|
353
|
+
compliance_status = ComplianceStatus.ERROR
|
|
354
|
+
evaluation_reason = f"Error checking audit log policy for account {account_id}: {str(e)}"
|
|
355
|
+
|
|
356
|
+
return ComplianceResult(
|
|
357
|
+
resource_id=account_id,
|
|
358
|
+
resource_type="AWS::::Account",
|
|
359
|
+
compliance_status=compliance_status,
|
|
360
|
+
evaluation_reason=evaluation_reason,
|
|
361
|
+
config_rule_name=self.rule_name,
|
|
362
|
+
region=region
|
|
363
|
+
)
|