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,512 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CIS Control 3.3 - Advanced Security Controls
|
|
3
|
+
Advanced security controls for comprehensive security coverage.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import List, Dict, Any, Optional
|
|
8
|
+
import boto3
|
|
9
|
+
import json
|
|
10
|
+
from botocore.exceptions import ClientError, NoCredentialsError
|
|
11
|
+
|
|
12
|
+
from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
|
|
13
|
+
from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
|
|
14
|
+
from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EC2ManagedInstanceAssociationComplianceStatusCheckAssessment(BaseConfigRuleAssessment):
|
|
20
|
+
"""
|
|
21
|
+
CIS Control 1.1/2.4/4.1 - Systems Management
|
|
22
|
+
AWS Config Rule: ec2-managedinstance-association-compliance-status-check
|
|
23
|
+
|
|
24
|
+
Ensures EC2 instances have proper Systems Manager associations for compliance tracking.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
super().__init__(
|
|
29
|
+
rule_name="ec2-managedinstance-association-compliance-status-check",
|
|
30
|
+
control_id="3.3",
|
|
31
|
+
resource_types=["AWS::EC2::Instance"]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
35
|
+
"""Get all EC2 instances that should be managed by SSM."""
|
|
36
|
+
if resource_type != "AWS::EC2::Instance":
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
ec2_client = aws_factory.get_client('ec2', region)
|
|
41
|
+
ssm_client = aws_factory.get_client('ssm', region)
|
|
42
|
+
|
|
43
|
+
# Get all running EC2 instances
|
|
44
|
+
paginator = ec2_client.get_paginator('describe_instances')
|
|
45
|
+
instances = []
|
|
46
|
+
|
|
47
|
+
for page in paginator.paginate(
|
|
48
|
+
Filters=[
|
|
49
|
+
{'Name': 'instance-state-name', 'Values': ['running']}
|
|
50
|
+
]
|
|
51
|
+
):
|
|
52
|
+
for reservation in page['Reservations']:
|
|
53
|
+
for instance in reservation['Instances']:
|
|
54
|
+
instance_id = instance['InstanceId']
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
# Check if instance is managed by SSM
|
|
58
|
+
ssm_response = ssm_client.describe_instance_information(
|
|
59
|
+
Filters=[
|
|
60
|
+
{'Key': 'InstanceIds', 'Values': [instance_id]}
|
|
61
|
+
]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
managed_instances = ssm_response.get('InstanceInformationList', [])
|
|
65
|
+
is_managed = len(managed_instances) > 0
|
|
66
|
+
|
|
67
|
+
if is_managed:
|
|
68
|
+
# Get association compliance status
|
|
69
|
+
try:
|
|
70
|
+
compliance_response = ssm_client.list_compliance_items(
|
|
71
|
+
ResourceId=instance_id,
|
|
72
|
+
ResourceType='ManagedInstance'
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
compliance_items = compliance_response.get('ComplianceItems', [])
|
|
76
|
+
association_compliance = []
|
|
77
|
+
|
|
78
|
+
for item in compliance_items:
|
|
79
|
+
if item.get('ComplianceType') == 'Association':
|
|
80
|
+
association_compliance.append({
|
|
81
|
+
'Id': item.get('Id', ''),
|
|
82
|
+
'Status': item.get('Status', ''),
|
|
83
|
+
'Severity': item.get('Severity', ''),
|
|
84
|
+
'Title': item.get('Title', '')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
instances.append({
|
|
88
|
+
'InstanceId': instance_id,
|
|
89
|
+
'InstanceType': instance.get('InstanceType', ''),
|
|
90
|
+
'Platform': instance.get('Platform', 'Linux'),
|
|
91
|
+
'VpcId': instance.get('VpcId', ''),
|
|
92
|
+
'IsSSMManaged': True,
|
|
93
|
+
'AssociationCompliance': association_compliance,
|
|
94
|
+
'HasAssociations': len(association_compliance) > 0
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
except ClientError as e:
|
|
98
|
+
# Instance is managed but can't get compliance info
|
|
99
|
+
instances.append({
|
|
100
|
+
'InstanceId': instance_id,
|
|
101
|
+
'InstanceType': instance.get('InstanceType', ''),
|
|
102
|
+
'Platform': instance.get('Platform', 'Linux'),
|
|
103
|
+
'VpcId': instance.get('VpcId', ''),
|
|
104
|
+
'IsSSMManaged': True,
|
|
105
|
+
'AssociationCompliance': [],
|
|
106
|
+
'HasAssociations': None # Unknown
|
|
107
|
+
})
|
|
108
|
+
else:
|
|
109
|
+
# Instance is not managed by SSM
|
|
110
|
+
instances.append({
|
|
111
|
+
'InstanceId': instance_id,
|
|
112
|
+
'InstanceType': instance.get('InstanceType', ''),
|
|
113
|
+
'Platform': instance.get('Platform', 'Linux'),
|
|
114
|
+
'VpcId': instance.get('VpcId', ''),
|
|
115
|
+
'IsSSMManaged': False,
|
|
116
|
+
'AssociationCompliance': [],
|
|
117
|
+
'HasAssociations': False
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
except ClientError as e:
|
|
121
|
+
logger.warning(f"Error checking SSM status for instance {instance_id}: {e}")
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
logger.debug(f"Found {len(instances)} running EC2 instances in {region}")
|
|
125
|
+
return instances
|
|
126
|
+
|
|
127
|
+
except ClientError as e:
|
|
128
|
+
logger.error(f"Error retrieving EC2 instances in {region}: {e}")
|
|
129
|
+
raise
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error(f"Unexpected error retrieving EC2 instances in {region}: {e}")
|
|
132
|
+
raise
|
|
133
|
+
|
|
134
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
135
|
+
"""Evaluate if EC2 instance has proper SSM association compliance."""
|
|
136
|
+
instance_id = resource.get('InstanceId', 'unknown')
|
|
137
|
+
is_ssm_managed = resource.get('IsSSMManaged', False)
|
|
138
|
+
has_associations = resource.get('HasAssociations', False)
|
|
139
|
+
association_compliance = resource.get('AssociationCompliance', [])
|
|
140
|
+
|
|
141
|
+
if not is_ssm_managed:
|
|
142
|
+
return ComplianceResult(
|
|
143
|
+
resource_id=instance_id,
|
|
144
|
+
resource_type="AWS::EC2::Instance",
|
|
145
|
+
compliance_status=ComplianceStatus.NON_COMPLIANT,
|
|
146
|
+
evaluation_reason="EC2 instance is not managed by Systems Manager",
|
|
147
|
+
config_rule_name=self.rule_name,
|
|
148
|
+
region=region
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if has_associations is None:
|
|
152
|
+
return ComplianceResult(
|
|
153
|
+
resource_id=instance_id,
|
|
154
|
+
resource_type="AWS::EC2::Instance",
|
|
155
|
+
compliance_status=ComplianceStatus.ERROR,
|
|
156
|
+
evaluation_reason="Unable to determine association compliance status",
|
|
157
|
+
config_rule_name=self.rule_name,
|
|
158
|
+
region=region
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if not has_associations:
|
|
162
|
+
return ComplianceResult(
|
|
163
|
+
resource_id=instance_id,
|
|
164
|
+
resource_type="AWS::EC2::Instance",
|
|
165
|
+
compliance_status=ComplianceStatus.NON_COMPLIANT,
|
|
166
|
+
evaluation_reason="EC2 instance has no SSM associations configured",
|
|
167
|
+
config_rule_name=self.rule_name,
|
|
168
|
+
region=region
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Check compliance status of associations
|
|
172
|
+
non_compliant_associations = [
|
|
173
|
+
assoc for assoc in association_compliance
|
|
174
|
+
if assoc.get('Status') != 'COMPLIANT'
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
if non_compliant_associations:
|
|
178
|
+
return ComplianceResult(
|
|
179
|
+
resource_id=instance_id,
|
|
180
|
+
resource_type="AWS::EC2::Instance",
|
|
181
|
+
compliance_status=ComplianceStatus.NON_COMPLIANT,
|
|
182
|
+
evaluation_reason=f"EC2 instance has {len(non_compliant_associations)} non-compliant SSM associations",
|
|
183
|
+
config_rule_name=self.rule_name,
|
|
184
|
+
region=region
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
return ComplianceResult(
|
|
188
|
+
resource_id=instance_id,
|
|
189
|
+
resource_type="AWS::EC2::Instance",
|
|
190
|
+
compliance_status=ComplianceStatus.COMPLIANT,
|
|
191
|
+
evaluation_reason=f"EC2 instance has {len(association_compliance)} compliant SSM associations",
|
|
192
|
+
config_rule_name=self.rule_name,
|
|
193
|
+
region=region
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class EMRKerberosEnabledAssessment(BaseConfigRuleAssessment):
|
|
198
|
+
"""
|
|
199
|
+
CIS Control 3.3 - Configure Data Access Control Lists
|
|
200
|
+
AWS Config Rule: emr-kerberos-enabled
|
|
201
|
+
|
|
202
|
+
Ensures EMR clusters have Kerberos authentication enabled to prevent unauthorized access.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def __init__(self):
|
|
206
|
+
super().__init__(
|
|
207
|
+
rule_name="emr-kerberos-enabled",
|
|
208
|
+
control_id="3.3",
|
|
209
|
+
resource_types=["AWS::EMR::Cluster"]
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
213
|
+
"""Get all EMR clusters in the region."""
|
|
214
|
+
if resource_type != "AWS::EMR::Cluster":
|
|
215
|
+
return []
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
emr_client = aws_factory.get_client('emr', region)
|
|
219
|
+
|
|
220
|
+
# Get all EMR clusters
|
|
221
|
+
paginator = emr_client.get_paginator('list_clusters')
|
|
222
|
+
clusters = []
|
|
223
|
+
|
|
224
|
+
for page in paginator.paginate():
|
|
225
|
+
for cluster_summary in page['Clusters']:
|
|
226
|
+
cluster_id = cluster_summary['Id']
|
|
227
|
+
state = cluster_summary.get('Status', {}).get('State', '')
|
|
228
|
+
|
|
229
|
+
# Skip terminated clusters
|
|
230
|
+
if state in ['TERMINATED', 'TERMINATED_WITH_ERRORS']:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
# Get detailed cluster information
|
|
235
|
+
cluster_response = emr_client.describe_cluster(ClusterId=cluster_id)
|
|
236
|
+
cluster = cluster_response['Cluster']
|
|
237
|
+
|
|
238
|
+
# Check for Kerberos configuration
|
|
239
|
+
kerberos_attributes = cluster.get('KerberosAttributes', {})
|
|
240
|
+
has_kerberos = bool(kerberos_attributes)
|
|
241
|
+
|
|
242
|
+
clusters.append({
|
|
243
|
+
'ClusterId': cluster_id,
|
|
244
|
+
'Name': cluster.get('Name', ''),
|
|
245
|
+
'State': state,
|
|
246
|
+
'ReleaseLabel': cluster.get('ReleaseLabel', ''),
|
|
247
|
+
'Applications': [app['Name'] for app in cluster.get('Applications', [])],
|
|
248
|
+
'HasKerberos': has_kerberos,
|
|
249
|
+
'KerberosAttributes': kerberos_attributes
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
except ClientError as e:
|
|
253
|
+
logger.warning(f"Error getting details for EMR cluster {cluster_id}: {e}")
|
|
254
|
+
continue
|
|
255
|
+
|
|
256
|
+
logger.debug(f"Found {len(clusters)} active EMR clusters in {region}")
|
|
257
|
+
return clusters
|
|
258
|
+
|
|
259
|
+
except ClientError as e:
|
|
260
|
+
logger.error(f"Error retrieving EMR clusters in {region}: {e}")
|
|
261
|
+
raise
|
|
262
|
+
except Exception as e:
|
|
263
|
+
logger.error(f"Unexpected error retrieving EMR clusters in {region}: {e}")
|
|
264
|
+
raise
|
|
265
|
+
|
|
266
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
267
|
+
"""Evaluate if EMR cluster has Kerberos authentication enabled."""
|
|
268
|
+
cluster_id = resource.get('ClusterId', 'unknown')
|
|
269
|
+
state = resource.get('State', '')
|
|
270
|
+
has_kerberos = resource.get('HasKerberos', False)
|
|
271
|
+
|
|
272
|
+
# Skip clusters that are not running
|
|
273
|
+
if state in ['TERMINATED', 'TERMINATED_WITH_ERRORS', 'TERMINATING']:
|
|
274
|
+
return ComplianceResult(
|
|
275
|
+
resource_id=cluster_id,
|
|
276
|
+
resource_type="AWS::EMR::Cluster",
|
|
277
|
+
compliance_status=ComplianceStatus.NOT_APPLICABLE,
|
|
278
|
+
evaluation_reason=f"EMR cluster is in state '{state}'",
|
|
279
|
+
config_rule_name=self.rule_name,
|
|
280
|
+
region=region
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
if has_kerberos:
|
|
284
|
+
return ComplianceResult(
|
|
285
|
+
resource_id=cluster_id,
|
|
286
|
+
resource_type="AWS::EMR::Cluster",
|
|
287
|
+
compliance_status=ComplianceStatus.COMPLIANT,
|
|
288
|
+
evaluation_reason="EMR cluster has Kerberos authentication enabled",
|
|
289
|
+
config_rule_name=self.rule_name,
|
|
290
|
+
region=region
|
|
291
|
+
)
|
|
292
|
+
else:
|
|
293
|
+
return ComplianceResult(
|
|
294
|
+
resource_id=cluster_id,
|
|
295
|
+
resource_type="AWS::EMR::Cluster",
|
|
296
|
+
compliance_status=ComplianceStatus.NON_COMPLIANT,
|
|
297
|
+
evaluation_reason="EMR cluster does not have Kerberos authentication enabled",
|
|
298
|
+
config_rule_name=self.rule_name,
|
|
299
|
+
region=region
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class LambdaInsideVPCAssessment(BaseConfigRuleAssessment):
|
|
304
|
+
"""
|
|
305
|
+
CIS Control 3.3 - Configure Data Access Control Lists
|
|
306
|
+
AWS Config Rule: lambda-inside-vpc
|
|
307
|
+
|
|
308
|
+
Ensures Lambda functions are deployed within VPC when needed for network isolation.
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
def __init__(self):
|
|
312
|
+
super().__init__(
|
|
313
|
+
rule_name="lambda-inside-vpc",
|
|
314
|
+
control_id="3.3",
|
|
315
|
+
resource_types=["AWS::Lambda::Function"]
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
319
|
+
"""Get all Lambda functions in the region."""
|
|
320
|
+
if resource_type != "AWS::Lambda::Function":
|
|
321
|
+
return []
|
|
322
|
+
|
|
323
|
+
try:
|
|
324
|
+
lambda_client = aws_factory.get_client('lambda', region)
|
|
325
|
+
|
|
326
|
+
# Get all Lambda functions
|
|
327
|
+
paginator = lambda_client.get_paginator('list_functions')
|
|
328
|
+
functions = []
|
|
329
|
+
|
|
330
|
+
for page in paginator.paginate():
|
|
331
|
+
for function in page['Functions']:
|
|
332
|
+
vpc_config = function.get('VpcConfig', {})
|
|
333
|
+
|
|
334
|
+
functions.append({
|
|
335
|
+
'FunctionName': function['FunctionName'],
|
|
336
|
+
'FunctionArn': function['FunctionArn'],
|
|
337
|
+
'Runtime': function.get('Runtime', ''),
|
|
338
|
+
'Role': function.get('Role', ''),
|
|
339
|
+
'VpcConfig': vpc_config,
|
|
340
|
+
'VpcId': vpc_config.get('VpcId', ''),
|
|
341
|
+
'SubnetIds': vpc_config.get('SubnetIds', []),
|
|
342
|
+
'SecurityGroupIds': vpc_config.get('SecurityGroupIds', []),
|
|
343
|
+
'IsInVPC': bool(vpc_config.get('VpcId'))
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
logger.debug(f"Found {len(functions)} Lambda functions in {region}")
|
|
347
|
+
return functions
|
|
348
|
+
|
|
349
|
+
except ClientError as e:
|
|
350
|
+
logger.error(f"Error retrieving Lambda functions in {region}: {e}")
|
|
351
|
+
raise
|
|
352
|
+
except Exception as e:
|
|
353
|
+
logger.error(f"Unexpected error retrieving Lambda functions in {region}: {e}")
|
|
354
|
+
raise
|
|
355
|
+
|
|
356
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
357
|
+
"""Evaluate if Lambda function is deployed within VPC."""
|
|
358
|
+
function_name = resource.get('FunctionName', 'unknown')
|
|
359
|
+
is_in_vpc = resource.get('IsInVPC', False)
|
|
360
|
+
vpc_id = resource.get('VpcId', '')
|
|
361
|
+
|
|
362
|
+
# Note: This rule is context-dependent. Some Lambda functions may not need VPC access.
|
|
363
|
+
# For this implementation, we'll consider functions that access VPC resources should be in VPC.
|
|
364
|
+
# This is a simplified check - in practice, you might want to check function tags or naming patterns.
|
|
365
|
+
|
|
366
|
+
if is_in_vpc:
|
|
367
|
+
return ComplianceResult(
|
|
368
|
+
resource_id=function_name,
|
|
369
|
+
resource_type="AWS::Lambda::Function",
|
|
370
|
+
compliance_status=ComplianceStatus.COMPLIANT,
|
|
371
|
+
evaluation_reason=f"Lambda function is deployed within VPC {vpc_id}",
|
|
372
|
+
config_rule_name=self.rule_name,
|
|
373
|
+
region=region
|
|
374
|
+
)
|
|
375
|
+
else:
|
|
376
|
+
# For this assessment, we'll mark as informational rather than non-compliant
|
|
377
|
+
# since not all Lambda functions need VPC access
|
|
378
|
+
return ComplianceResult(
|
|
379
|
+
resource_id=function_name,
|
|
380
|
+
resource_type="AWS::Lambda::Function",
|
|
381
|
+
compliance_status=ComplianceStatus.NOT_APPLICABLE,
|
|
382
|
+
evaluation_reason="Lambda function is not deployed within VPC (may not require VPC access)",
|
|
383
|
+
config_rule_name=self.rule_name,
|
|
384
|
+
region=region
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
class ECSTaskDefinitionUserForHostModeCheckAssessment(BaseConfigRuleAssessment):
|
|
389
|
+
"""
|
|
390
|
+
CIS Control 3.3 - Configure Data Access Control Lists
|
|
391
|
+
AWS Config Rule: ecs-task-definition-user-for-host-mode-check
|
|
392
|
+
|
|
393
|
+
Ensures ECS tasks in host mode do not run with elevated privileges to prevent container privilege escalation.
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
def __init__(self):
|
|
397
|
+
super().__init__(
|
|
398
|
+
rule_name="ecs-task-definition-user-for-host-mode-check",
|
|
399
|
+
control_id="3.3",
|
|
400
|
+
resource_types=["AWS::ECS::TaskDefinition"]
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
404
|
+
"""Get all ECS task definitions in the region."""
|
|
405
|
+
if resource_type != "AWS::ECS::TaskDefinition":
|
|
406
|
+
return []
|
|
407
|
+
|
|
408
|
+
try:
|
|
409
|
+
ecs_client = aws_factory.get_client('ecs', region)
|
|
410
|
+
|
|
411
|
+
# Get all task definition families
|
|
412
|
+
families_response = ecs_client.list_task_definition_families(status='ACTIVE')
|
|
413
|
+
task_definitions = []
|
|
414
|
+
|
|
415
|
+
for family in families_response.get('families', []):
|
|
416
|
+
try:
|
|
417
|
+
# Get the latest revision of each family
|
|
418
|
+
list_response = ecs_client.list_task_definitions(
|
|
419
|
+
familyPrefix=family,
|
|
420
|
+
status='ACTIVE',
|
|
421
|
+
sort='DESC',
|
|
422
|
+
maxResults=1
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
if list_response.get('taskDefinitionArns'):
|
|
426
|
+
task_def_arn = list_response['taskDefinitionArns'][0]
|
|
427
|
+
|
|
428
|
+
# Get detailed task definition
|
|
429
|
+
describe_response = ecs_client.describe_task_definition(
|
|
430
|
+
taskDefinition=task_def_arn
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
task_def = describe_response['taskDefinition']
|
|
434
|
+
network_mode = task_def.get('networkMode', 'bridge')
|
|
435
|
+
|
|
436
|
+
# Analyze container definitions for host mode issues
|
|
437
|
+
containers = task_def.get('containerDefinitions', [])
|
|
438
|
+
host_mode_issues = []
|
|
439
|
+
|
|
440
|
+
if network_mode == 'host':
|
|
441
|
+
for container in containers:
|
|
442
|
+
container_name = container.get('name', 'unknown')
|
|
443
|
+
user = container.get('user', '')
|
|
444
|
+
privileged = container.get('privileged', False)
|
|
445
|
+
|
|
446
|
+
# Check for privilege escalation risks in host mode
|
|
447
|
+
if privileged:
|
|
448
|
+
host_mode_issues.append(f"Container '{container_name}' runs in privileged mode")
|
|
449
|
+
elif not user or user == 'root' or user == '0':
|
|
450
|
+
host_mode_issues.append(f"Container '{container_name}' runs as root user")
|
|
451
|
+
|
|
452
|
+
task_definitions.append({
|
|
453
|
+
'TaskDefinitionArn': task_def_arn,
|
|
454
|
+
'Family': task_def.get('family', ''),
|
|
455
|
+
'Revision': task_def.get('revision', 0),
|
|
456
|
+
'NetworkMode': network_mode,
|
|
457
|
+
'ContainerCount': len(containers),
|
|
458
|
+
'IsHostMode': network_mode == 'host',
|
|
459
|
+
'HostModeIssues': host_mode_issues,
|
|
460
|
+
'HasHostModeIssues': len(host_mode_issues) > 0
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
except ClientError as e:
|
|
464
|
+
logger.warning(f"Error getting task definition details for family {family}: {e}")
|
|
465
|
+
continue
|
|
466
|
+
|
|
467
|
+
logger.debug(f"Found {len(task_definitions)} active ECS task definitions in {region}")
|
|
468
|
+
return task_definitions
|
|
469
|
+
|
|
470
|
+
except ClientError as e:
|
|
471
|
+
logger.error(f"Error retrieving ECS task definitions in {region}: {e}")
|
|
472
|
+
raise
|
|
473
|
+
except Exception as e:
|
|
474
|
+
logger.error(f"Unexpected error retrieving ECS task definitions in {region}: {e}")
|
|
475
|
+
raise
|
|
476
|
+
|
|
477
|
+
def _evaluate_resource_compliance(self, resource: Dict[str, Any], aws_factory: AWSClientFactory, region: str) -> ComplianceResult:
|
|
478
|
+
"""Evaluate if ECS task definition has proper user configuration for host mode."""
|
|
479
|
+
task_def_arn = resource.get('TaskDefinitionArn', 'unknown')
|
|
480
|
+
family = resource.get('Family', 'unknown')
|
|
481
|
+
is_host_mode = resource.get('IsHostMode', False)
|
|
482
|
+
has_host_mode_issues = resource.get('HasHostModeIssues', False)
|
|
483
|
+
host_mode_issues = resource.get('HostModeIssues', [])
|
|
484
|
+
|
|
485
|
+
if not is_host_mode:
|
|
486
|
+
return ComplianceResult(
|
|
487
|
+
resource_id=task_def_arn,
|
|
488
|
+
resource_type="AWS::ECS::TaskDefinition",
|
|
489
|
+
compliance_status=ComplianceStatus.NOT_APPLICABLE,
|
|
490
|
+
evaluation_reason="ECS task definition does not use host network mode",
|
|
491
|
+
config_rule_name=self.rule_name,
|
|
492
|
+
region=region
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
if has_host_mode_issues:
|
|
496
|
+
return ComplianceResult(
|
|
497
|
+
resource_id=task_def_arn,
|
|
498
|
+
resource_type="AWS::ECS::TaskDefinition",
|
|
499
|
+
compliance_status=ComplianceStatus.NON_COMPLIANT,
|
|
500
|
+
evaluation_reason=f"ECS task definition in host mode has privilege escalation risks: {'; '.join(host_mode_issues)}",
|
|
501
|
+
config_rule_name=self.rule_name,
|
|
502
|
+
region=region
|
|
503
|
+
)
|
|
504
|
+
else:
|
|
505
|
+
return ComplianceResult(
|
|
506
|
+
resource_id=task_def_arn,
|
|
507
|
+
resource_type="AWS::ECS::TaskDefinition",
|
|
508
|
+
compliance_status=ComplianceStatus.COMPLIANT,
|
|
509
|
+
evaluation_reason="ECS task definition in host mode has proper user configuration",
|
|
510
|
+
config_rule_name=self.rule_name,
|
|
511
|
+
region=region
|
|
512
|
+
)
|