aws-cis-controls-assessment 1.1.3__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aws_cis_assessment/__init__.py +4 -4
- aws_cis_assessment/config/rules/cis_controls_ig1.yaml +365 -2
- aws_cis_assessment/controls/ig1/control_access_analyzer.py +198 -0
- aws_cis_assessment/controls/ig1/control_access_asset_mgmt.py +360 -0
- aws_cis_assessment/controls/ig1/control_access_control.py +323 -0
- aws_cis_assessment/controls/ig1/control_backup_security.py +579 -0
- aws_cis_assessment/controls/ig1/control_cloudfront_logging.py +215 -0
- aws_cis_assessment/controls/ig1/control_configuration_mgmt.py +407 -0
- aws_cis_assessment/controls/ig1/control_data_classification.py +255 -0
- aws_cis_assessment/controls/ig1/control_dynamodb_encryption.py +279 -0
- aws_cis_assessment/controls/ig1/control_ebs_encryption.py +177 -0
- aws_cis_assessment/controls/ig1/control_efs_encryption.py +243 -0
- aws_cis_assessment/controls/ig1/control_elb_logging.py +195 -0
- aws_cis_assessment/controls/ig1/control_guardduty.py +156 -0
- aws_cis_assessment/controls/ig1/control_inspector.py +184 -0
- aws_cis_assessment/controls/ig1/control_inventory.py +511 -0
- aws_cis_assessment/controls/ig1/control_macie.py +165 -0
- aws_cis_assessment/controls/ig1/control_messaging_encryption.py +419 -0
- aws_cis_assessment/controls/ig1/control_mfa.py +485 -0
- aws_cis_assessment/controls/ig1/control_network_security.py +194 -619
- aws_cis_assessment/controls/ig1/control_patch_management.py +626 -0
- aws_cis_assessment/controls/ig1/control_rds_encryption.py +228 -0
- aws_cis_assessment/controls/ig1/control_s3_encryption.py +383 -0
- aws_cis_assessment/controls/ig1/control_tls_ssl.py +556 -0
- aws_cis_assessment/controls/ig1/control_version_mgmt.py +329 -0
- aws_cis_assessment/controls/ig1/control_vpc_flow_logs.py +205 -0
- aws_cis_assessment/controls/ig1/control_waf_logging.py +226 -0
- aws_cis_assessment/core/models.py +20 -1
- aws_cis_assessment/core/scoring_engine.py +98 -1
- aws_cis_assessment/reporters/base_reporter.py +31 -1
- aws_cis_assessment/reporters/html_reporter.py +172 -11
- aws_cis_controls_assessment-1.2.0.dist-info/METADATA +320 -0
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/RECORD +39 -15
- docs/developer-guide.md +204 -5
- docs/user-guide.md +137 -4
- aws_cis_controls_assessment-1.1.3.dist-info/METADATA +0 -404
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/WHEEL +0 -0
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/entry_points.txt +0 -0
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CIS Control 1.1 - Inventory Management
|
|
3
|
+
Ensures proper asset inventory tracking across AWS resources.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import List, Dict, Any
|
|
8
|
+
from botocore.exceptions import ClientError
|
|
9
|
+
|
|
10
|
+
from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
|
|
11
|
+
from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
|
|
12
|
+
from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SSMInventoryEnabledAssessment(BaseConfigRuleAssessment):
|
|
18
|
+
"""
|
|
19
|
+
CIS Control 1.1 - Establish and Maintain Detailed Enterprise Asset Inventory
|
|
20
|
+
AWS Config Rule: ssm-inventory-enabled
|
|
21
|
+
|
|
22
|
+
Ensures AWS Systems Manager Inventory is enabled for asset tracking.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
super().__init__(
|
|
27
|
+
rule_name="ssm-inventory-enabled",
|
|
28
|
+
control_id="1.1",
|
|
29
|
+
resource_types=["AWS::::Account"]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
33
|
+
"""Check if SSM Inventory is configured."""
|
|
34
|
+
if resource_type != "AWS::::Account":
|
|
35
|
+
return []
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
ssm_client = aws_factory.get_client('ssm', region)
|
|
39
|
+
|
|
40
|
+
# Check for inventory associations
|
|
41
|
+
response = ssm_client.list_associations(
|
|
42
|
+
AssociationFilterList=[
|
|
43
|
+
{'key': 'AssociationName', 'value': 'AWS-GatherSoftwareInventory'}
|
|
44
|
+
]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
associations = response.get('Associations', [])
|
|
48
|
+
inventory_enabled = len(associations) > 0
|
|
49
|
+
|
|
50
|
+
return [{
|
|
51
|
+
'AccountId': region,
|
|
52
|
+
'InventoryEnabled': inventory_enabled,
|
|
53
|
+
'AssociationCount': len(associations)
|
|
54
|
+
}]
|
|
55
|
+
|
|
56
|
+
except ClientError as e:
|
|
57
|
+
logger.error(f"Error checking SSM Inventory in {region}: {e}")
|
|
58
|
+
return []
|
|
59
|
+
|
|
60
|
+
def _evaluate_resource_compliance(
|
|
61
|
+
self,
|
|
62
|
+
resource: Dict[str, Any],
|
|
63
|
+
aws_factory: AWSClientFactory,
|
|
64
|
+
region: str
|
|
65
|
+
) -> ComplianceResult:
|
|
66
|
+
"""Evaluate if SSM Inventory is enabled."""
|
|
67
|
+
inventory_enabled = resource.get('InventoryEnabled', False)
|
|
68
|
+
association_count = resource.get('AssociationCount', 0)
|
|
69
|
+
|
|
70
|
+
if inventory_enabled:
|
|
71
|
+
evaluation_reason = f"SSM Inventory is enabled with {association_count} association(s)"
|
|
72
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
73
|
+
else:
|
|
74
|
+
evaluation_reason = "SSM Inventory is not enabled"
|
|
75
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
76
|
+
|
|
77
|
+
return ComplianceResult(
|
|
78
|
+
resource_id=f"account-{region}",
|
|
79
|
+
resource_type="AWS::::Account",
|
|
80
|
+
compliance_status=compliance_status,
|
|
81
|
+
evaluation_reason=evaluation_reason,
|
|
82
|
+
config_rule_name=self.rule_name,
|
|
83
|
+
region=region
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
87
|
+
"""Get remediation steps for enabling SSM Inventory."""
|
|
88
|
+
return [
|
|
89
|
+
"1. Enable SSM Inventory using AWS CLI:",
|
|
90
|
+
" aws ssm create-association \\",
|
|
91
|
+
" --name AWS-GatherSoftwareInventory \\",
|
|
92
|
+
" --targets Key=InstanceIds,Values=* \\",
|
|
93
|
+
" --schedule-expression 'rate(30 minutes)'",
|
|
94
|
+
"",
|
|
95
|
+
"2. Console method:",
|
|
96
|
+
" - Navigate to Systems Manager",
|
|
97
|
+
" - Click 'Inventory' in left menu",
|
|
98
|
+
" - Click 'Setup Inventory'",
|
|
99
|
+
" - Configure targets and schedule",
|
|
100
|
+
" - Click 'Setup Inventory'",
|
|
101
|
+
"",
|
|
102
|
+
"Priority: MEDIUM - Important for asset tracking",
|
|
103
|
+
"Effort: Low - Quick setup",
|
|
104
|
+
"",
|
|
105
|
+
"AWS Documentation:",
|
|
106
|
+
"https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-inventory-configuring.html"
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ConfigEnabledAllRegionsAssessment(BaseConfigRuleAssessment):
|
|
111
|
+
"""
|
|
112
|
+
CIS Control 1.1 - Establish and Maintain Detailed Enterprise Asset Inventory
|
|
113
|
+
AWS Config Rule: config-enabled-all-regions
|
|
114
|
+
|
|
115
|
+
Ensures AWS Config is enabled in all regions for comprehensive resource tracking.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(self):
|
|
119
|
+
super().__init__(
|
|
120
|
+
rule_name="config-enabled-all-regions",
|
|
121
|
+
control_id="1.1",
|
|
122
|
+
resource_types=["AWS::::Account"]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
126
|
+
"""Check AWS Config status across all regions."""
|
|
127
|
+
if resource_type != "AWS::::Account":
|
|
128
|
+
return []
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
ec2_client = aws_factory.get_client('ec2', 'us-east-1')
|
|
132
|
+
all_regions = [r['RegionName'] for r in ec2_client.describe_regions()['Regions']]
|
|
133
|
+
|
|
134
|
+
enabled_regions = []
|
|
135
|
+
disabled_regions = []
|
|
136
|
+
|
|
137
|
+
for check_region in all_regions:
|
|
138
|
+
try:
|
|
139
|
+
config_client = aws_factory.get_client('config', check_region)
|
|
140
|
+
recorders = config_client.describe_configuration_recorders()
|
|
141
|
+
|
|
142
|
+
if recorders.get('ConfigurationRecorders'):
|
|
143
|
+
# Check if recorder is recording
|
|
144
|
+
status = config_client.describe_configuration_recorder_status()
|
|
145
|
+
if status.get('ConfigurationRecordersStatus'):
|
|
146
|
+
if status['ConfigurationRecordersStatus'][0].get('recording'):
|
|
147
|
+
enabled_regions.append(check_region)
|
|
148
|
+
else:
|
|
149
|
+
disabled_regions.append(check_region)
|
|
150
|
+
else:
|
|
151
|
+
disabled_regions.append(check_region)
|
|
152
|
+
else:
|
|
153
|
+
disabled_regions.append(check_region)
|
|
154
|
+
except ClientError:
|
|
155
|
+
disabled_regions.append(check_region)
|
|
156
|
+
|
|
157
|
+
return [{
|
|
158
|
+
'AccountId': 'global',
|
|
159
|
+
'EnabledRegions': enabled_regions,
|
|
160
|
+
'DisabledRegions': disabled_regions,
|
|
161
|
+
'TotalRegions': len(all_regions)
|
|
162
|
+
}]
|
|
163
|
+
|
|
164
|
+
except ClientError as e:
|
|
165
|
+
logger.error(f"Error checking Config status: {e}")
|
|
166
|
+
return []
|
|
167
|
+
|
|
168
|
+
def _evaluate_resource_compliance(
|
|
169
|
+
self,
|
|
170
|
+
resource: Dict[str, Any],
|
|
171
|
+
aws_factory: AWSClientFactory,
|
|
172
|
+
region: str
|
|
173
|
+
) -> ComplianceResult:
|
|
174
|
+
"""Evaluate if Config is enabled in all regions."""
|
|
175
|
+
enabled_regions = resource.get('EnabledRegions', [])
|
|
176
|
+
disabled_regions = resource.get('DisabledRegions', [])
|
|
177
|
+
total_regions = resource.get('TotalRegions', 0)
|
|
178
|
+
|
|
179
|
+
if not disabled_regions:
|
|
180
|
+
evaluation_reason = f"AWS Config is enabled in all {total_regions} regions"
|
|
181
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
182
|
+
else:
|
|
183
|
+
evaluation_reason = (
|
|
184
|
+
f"AWS Config is disabled in {len(disabled_regions)} region(s): "
|
|
185
|
+
f"{', '.join(disabled_regions[:5])}"
|
|
186
|
+
)
|
|
187
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
188
|
+
|
|
189
|
+
return ComplianceResult(
|
|
190
|
+
resource_id="account-config-status",
|
|
191
|
+
resource_type="AWS::::Account",
|
|
192
|
+
compliance_status=compliance_status,
|
|
193
|
+
evaluation_reason=evaluation_reason,
|
|
194
|
+
config_rule_name=self.rule_name,
|
|
195
|
+
region=region
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
199
|
+
"""Get remediation steps for enabling Config in all regions."""
|
|
200
|
+
return [
|
|
201
|
+
"1. Enable AWS Config in a region:",
|
|
202
|
+
" aws configservice put-configuration-recorder \\",
|
|
203
|
+
" --configuration-recorder name=default,roleARN=<role-arn> \\",
|
|
204
|
+
" --recording-group allSupported=true,includeGlobalResourceTypes=true",
|
|
205
|
+
"",
|
|
206
|
+
" aws configservice put-delivery-channel \\",
|
|
207
|
+
" --delivery-channel name=default,s3BucketName=<bucket-name>",
|
|
208
|
+
"",
|
|
209
|
+
" aws configservice start-configuration-recorder \\",
|
|
210
|
+
" --configuration-recorder-name default",
|
|
211
|
+
"",
|
|
212
|
+
"2. Enable in all regions (script):",
|
|
213
|
+
" for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do",
|
|
214
|
+
" aws configservice put-configuration-recorder --region $region ...",
|
|
215
|
+
" done",
|
|
216
|
+
"",
|
|
217
|
+
"Priority: HIGH - Essential for compliance tracking",
|
|
218
|
+
"Effort: Medium - Requires setup in each region",
|
|
219
|
+
"",
|
|
220
|
+
"AWS Documentation:",
|
|
221
|
+
"https://docs.aws.amazon.com/config/latest/developerguide/gs-console.html"
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class AMIInventoryTrackingAssessment(BaseConfigRuleAssessment):
|
|
226
|
+
"""
|
|
227
|
+
CIS Control 1.1 - Establish and Maintain Detailed Enterprise Asset Inventory
|
|
228
|
+
AWS Config Rule: ami-inventory-tracking
|
|
229
|
+
|
|
230
|
+
Ensures AMIs are properly tagged for inventory tracking.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def __init__(self):
|
|
234
|
+
super().__init__(
|
|
235
|
+
rule_name="ami-inventory-tracking",
|
|
236
|
+
control_id="1.1",
|
|
237
|
+
resource_types=["AWS::EC2::Image"]
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
241
|
+
"""Get all AMIs owned by the account."""
|
|
242
|
+
if resource_type != "AWS::EC2::Image":
|
|
243
|
+
return []
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
ec2_client = aws_factory.get_client('ec2', region)
|
|
247
|
+
|
|
248
|
+
# Get account ID
|
|
249
|
+
sts_client = aws_factory.get_client('sts', region)
|
|
250
|
+
account_id = sts_client.get_caller_identity()['Account']
|
|
251
|
+
|
|
252
|
+
# List AMIs owned by this account
|
|
253
|
+
response = ec2_client.describe_images(Owners=[account_id])
|
|
254
|
+
images = response.get('Images', [])
|
|
255
|
+
|
|
256
|
+
amis = []
|
|
257
|
+
for image in images:
|
|
258
|
+
tags = {tag['Key']: tag['Value'] for tag in image.get('Tags', [])}
|
|
259
|
+
|
|
260
|
+
amis.append({
|
|
261
|
+
'ImageId': image.get('ImageId'),
|
|
262
|
+
'Name': image.get('Name', 'unnamed'),
|
|
263
|
+
'Tags': tags,
|
|
264
|
+
'HasInventoryTags': bool(tags.get('Environment') or tags.get('Owner') or tags.get('Application'))
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
return amis
|
|
268
|
+
|
|
269
|
+
except ClientError as e:
|
|
270
|
+
logger.error(f"Error retrieving AMIs in {region}: {e}")
|
|
271
|
+
return []
|
|
272
|
+
|
|
273
|
+
def _evaluate_resource_compliance(
|
|
274
|
+
self,
|
|
275
|
+
resource: Dict[str, Any],
|
|
276
|
+
aws_factory: AWSClientFactory,
|
|
277
|
+
region: str
|
|
278
|
+
) -> ComplianceResult:
|
|
279
|
+
"""Evaluate if AMI has proper inventory tags."""
|
|
280
|
+
image_id = resource.get('ImageId', 'unknown')
|
|
281
|
+
name = resource.get('Name', 'unnamed')
|
|
282
|
+
has_inventory_tags = resource.get('HasInventoryTags', False)
|
|
283
|
+
|
|
284
|
+
if has_inventory_tags:
|
|
285
|
+
evaluation_reason = f"AMI '{name}' has proper inventory tags"
|
|
286
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
287
|
+
else:
|
|
288
|
+
evaluation_reason = f"AMI '{name}' is missing inventory tags (Environment, Owner, or Application)"
|
|
289
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
290
|
+
|
|
291
|
+
return ComplianceResult(
|
|
292
|
+
resource_id=image_id,
|
|
293
|
+
resource_type="AWS::EC2::Image",
|
|
294
|
+
compliance_status=compliance_status,
|
|
295
|
+
evaluation_reason=evaluation_reason,
|
|
296
|
+
config_rule_name=self.rule_name,
|
|
297
|
+
region=region
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
301
|
+
"""Get remediation steps for AMI inventory tagging."""
|
|
302
|
+
return [
|
|
303
|
+
"1. Tag an AMI with inventory information:",
|
|
304
|
+
" aws ec2 create-tags \\",
|
|
305
|
+
" --resources <ami-id> \\",
|
|
306
|
+
" --tags Key=Environment,Value=production \\",
|
|
307
|
+
" Key=Owner,Value=team-name \\",
|
|
308
|
+
" Key=Application,Value=app-name",
|
|
309
|
+
"",
|
|
310
|
+
"2. Tag multiple AMIs (script):",
|
|
311
|
+
" for ami in $(aws ec2 describe-images --owners self --query 'Images[].ImageId' --output text); do",
|
|
312
|
+
" aws ec2 create-tags --resources $ami --tags Key=Environment,Value=production",
|
|
313
|
+
" done",
|
|
314
|
+
"",
|
|
315
|
+
"Priority: MEDIUM - Important for asset management",
|
|
316
|
+
"Effort: Low - Simple tagging operation",
|
|
317
|
+
"",
|
|
318
|
+
"AWS Documentation:",
|
|
319
|
+
"https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html"
|
|
320
|
+
]
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class LambdaRuntimeInventoryAssessment(BaseConfigRuleAssessment):
|
|
324
|
+
"""
|
|
325
|
+
CIS Control 1.1 - Establish and Maintain Detailed Enterprise Asset Inventory
|
|
326
|
+
AWS Config Rule: lambda-runtime-inventory
|
|
327
|
+
|
|
328
|
+
Tracks Lambda function runtimes for inventory purposes.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
def __init__(self):
|
|
332
|
+
super().__init__(
|
|
333
|
+
rule_name="lambda-runtime-inventory",
|
|
334
|
+
control_id="1.1",
|
|
335
|
+
resource_types=["AWS::Lambda::Function"]
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
339
|
+
"""Get all Lambda functions and their runtimes."""
|
|
340
|
+
if resource_type != "AWS::Lambda::Function":
|
|
341
|
+
return []
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
lambda_client = aws_factory.get_client('lambda', region)
|
|
345
|
+
functions = []
|
|
346
|
+
|
|
347
|
+
paginator = lambda_client.get_paginator('list_functions')
|
|
348
|
+
for page in paginator.paginate():
|
|
349
|
+
for func in page.get('Functions', []):
|
|
350
|
+
functions.append({
|
|
351
|
+
'FunctionName': func.get('FunctionName'),
|
|
352
|
+
'FunctionArn': func.get('FunctionArn'),
|
|
353
|
+
'Runtime': func.get('Runtime', 'unknown'),
|
|
354
|
+
'LastModified': func.get('LastModified')
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
return functions
|
|
358
|
+
|
|
359
|
+
except ClientError as e:
|
|
360
|
+
logger.error(f"Error retrieving Lambda functions in {region}: {e}")
|
|
361
|
+
return []
|
|
362
|
+
|
|
363
|
+
def _evaluate_resource_compliance(
|
|
364
|
+
self,
|
|
365
|
+
resource: Dict[str, Any],
|
|
366
|
+
aws_factory: AWSClientFactory,
|
|
367
|
+
region: str
|
|
368
|
+
) -> ComplianceResult:
|
|
369
|
+
"""Evaluate Lambda function for inventory tracking."""
|
|
370
|
+
function_name = resource.get('FunctionName', 'unknown')
|
|
371
|
+
runtime = resource.get('Runtime', 'unknown')
|
|
372
|
+
|
|
373
|
+
# This is primarily for inventory - all functions are compliant
|
|
374
|
+
evaluation_reason = f"Lambda function '{function_name}' tracked with runtime: {runtime}"
|
|
375
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
376
|
+
|
|
377
|
+
return ComplianceResult(
|
|
378
|
+
resource_id=resource.get('FunctionArn', function_name),
|
|
379
|
+
resource_type="AWS::Lambda::Function",
|
|
380
|
+
compliance_status=compliance_status,
|
|
381
|
+
evaluation_reason=evaluation_reason,
|
|
382
|
+
config_rule_name=self.rule_name,
|
|
383
|
+
region=region
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
387
|
+
"""Get remediation steps for Lambda inventory."""
|
|
388
|
+
return [
|
|
389
|
+
"1. List all Lambda functions and runtimes:",
|
|
390
|
+
" aws lambda list-functions \\",
|
|
391
|
+
" --query 'Functions[].{Name:FunctionName,Runtime:Runtime}' \\",
|
|
392
|
+
" --output table",
|
|
393
|
+
"",
|
|
394
|
+
"2. Export to CSV for inventory:",
|
|
395
|
+
" aws lambda list-functions \\",
|
|
396
|
+
" --query 'Functions[].{Name:FunctionName,Runtime:Runtime,Modified:LastModified}' \\",
|
|
397
|
+
" --output json > lambda-inventory.json",
|
|
398
|
+
"",
|
|
399
|
+
"Priority: LOW - Informational inventory",
|
|
400
|
+
"Effort: None - Automated tracking",
|
|
401
|
+
"",
|
|
402
|
+
"AWS Documentation:",
|
|
403
|
+
"https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html"
|
|
404
|
+
]
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class IAMUserInventoryCheckAssessment(BaseConfigRuleAssessment):
|
|
408
|
+
"""
|
|
409
|
+
CIS Control 1.1 - Establish and Maintain Detailed Enterprise Asset Inventory
|
|
410
|
+
AWS Config Rule: iam-user-inventory-check
|
|
411
|
+
|
|
412
|
+
Ensures IAM users have proper inventory tags.
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
def __init__(self):
|
|
416
|
+
super().__init__(
|
|
417
|
+
rule_name="iam-user-inventory-check",
|
|
418
|
+
control_id="1.1",
|
|
419
|
+
resource_types=["AWS::IAM::User"]
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
423
|
+
"""Get all IAM users and their tags."""
|
|
424
|
+
if resource_type != "AWS::IAM::User":
|
|
425
|
+
return []
|
|
426
|
+
|
|
427
|
+
# IAM is global, only process in us-east-1
|
|
428
|
+
if region != 'us-east-1':
|
|
429
|
+
return []
|
|
430
|
+
|
|
431
|
+
try:
|
|
432
|
+
iam_client = aws_factory.get_client('iam', region)
|
|
433
|
+
users = []
|
|
434
|
+
|
|
435
|
+
paginator = iam_client.get_paginator('list_users')
|
|
436
|
+
for page in paginator.paginate():
|
|
437
|
+
for user in page.get('Users', []):
|
|
438
|
+
user_name = user.get('UserName')
|
|
439
|
+
|
|
440
|
+
try:
|
|
441
|
+
# Get user tags
|
|
442
|
+
tags_response = iam_client.list_user_tags(UserName=user_name)
|
|
443
|
+
tags = {tag['Key']: tag['Value'] for tag in tags_response.get('Tags', [])}
|
|
444
|
+
|
|
445
|
+
users.append({
|
|
446
|
+
'UserName': user_name,
|
|
447
|
+
'Arn': user.get('Arn'),
|
|
448
|
+
'Tags': tags,
|
|
449
|
+
'HasInventoryTags': bool(tags.get('Department') or tags.get('Owner') or tags.get('CostCenter'))
|
|
450
|
+
})
|
|
451
|
+
except ClientError:
|
|
452
|
+
users.append({
|
|
453
|
+
'UserName': user_name,
|
|
454
|
+
'Arn': user.get('Arn'),
|
|
455
|
+
'Tags': {},
|
|
456
|
+
'HasInventoryTags': False
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
return users
|
|
460
|
+
|
|
461
|
+
except ClientError as e:
|
|
462
|
+
logger.error(f"Error retrieving IAM users: {e}")
|
|
463
|
+
return []
|
|
464
|
+
|
|
465
|
+
def _evaluate_resource_compliance(
|
|
466
|
+
self,
|
|
467
|
+
resource: Dict[str, Any],
|
|
468
|
+
aws_factory: AWSClientFactory,
|
|
469
|
+
region: str
|
|
470
|
+
) -> ComplianceResult:
|
|
471
|
+
"""Evaluate if IAM user has proper inventory tags."""
|
|
472
|
+
user_name = resource.get('UserName', 'unknown')
|
|
473
|
+
has_inventory_tags = resource.get('HasInventoryTags', False)
|
|
474
|
+
|
|
475
|
+
if has_inventory_tags:
|
|
476
|
+
evaluation_reason = f"IAM user '{user_name}' has proper inventory tags"
|
|
477
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
478
|
+
else:
|
|
479
|
+
evaluation_reason = f"IAM user '{user_name}' is missing inventory tags (Department, Owner, or CostCenter)"
|
|
480
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
481
|
+
|
|
482
|
+
return ComplianceResult(
|
|
483
|
+
resource_id=resource.get('Arn', user_name),
|
|
484
|
+
resource_type="AWS::IAM::User",
|
|
485
|
+
compliance_status=compliance_status,
|
|
486
|
+
evaluation_reason=evaluation_reason,
|
|
487
|
+
config_rule_name=self.rule_name,
|
|
488
|
+
region=region
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
492
|
+
"""Get remediation steps for IAM user inventory tagging."""
|
|
493
|
+
return [
|
|
494
|
+
"1. Tag an IAM user with inventory information:",
|
|
495
|
+
" aws iam tag-user \\",
|
|
496
|
+
" --user-name <user-name> \\",
|
|
497
|
+
" --tags Key=Department,Value=engineering \\",
|
|
498
|
+
" Key=Owner,Value=manager-name \\",
|
|
499
|
+
" Key=CostCenter,Value=12345",
|
|
500
|
+
"",
|
|
501
|
+
"2. Tag multiple users (script):",
|
|
502
|
+
" for user in $(aws iam list-users --query 'Users[].UserName' --output text); do",
|
|
503
|
+
" aws iam tag-user --user-name $user --tags Key=Department,Value=engineering",
|
|
504
|
+
" done",
|
|
505
|
+
"",
|
|
506
|
+
"Priority: MEDIUM - Important for user management",
|
|
507
|
+
"Effort: Low - Simple tagging operation",
|
|
508
|
+
"",
|
|
509
|
+
"AWS Documentation:",
|
|
510
|
+
"https://docs.aws.amazon.com/IAM/latest/UserGuide/id_tags.html"
|
|
511
|
+
]
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CIS Control 3.1 - Amazon Macie Data Protection
|
|
3
|
+
Ensures Amazon Macie is enabled for sensitive data discovery.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import List, Dict, Any
|
|
8
|
+
from botocore.exceptions import ClientError
|
|
9
|
+
|
|
10
|
+
from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
|
|
11
|
+
from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
|
|
12
|
+
from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MacieEnabledAssessment(BaseConfigRuleAssessment):
|
|
18
|
+
"""
|
|
19
|
+
CIS Control 3.1 - Establish and Maintain a Data Management Process
|
|
20
|
+
AWS Config Rule: macie-enabled
|
|
21
|
+
|
|
22
|
+
Ensures Amazon Macie is enabled for automated sensitive data discovery.
|
|
23
|
+
Macie uses machine learning to discover, classify, and protect sensitive
|
|
24
|
+
data in S3 buckets, including PII, financial data, and credentials.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
super().__init__(
|
|
29
|
+
rule_name="macie-enabled",
|
|
30
|
+
control_id="3.1",
|
|
31
|
+
resource_types=["AWS::Macie::Session"]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
35
|
+
"""Get Amazon Macie configuration for the region."""
|
|
36
|
+
if resource_type != "AWS::Macie::Session":
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
macie_client = aws_factory.get_client('macie2', region)
|
|
41
|
+
|
|
42
|
+
# Check if Macie is enabled by getting session status
|
|
43
|
+
try:
|
|
44
|
+
session_response = macie_client.get_macie_session()
|
|
45
|
+
|
|
46
|
+
status = session_response.get('status', 'DISABLED')
|
|
47
|
+
finding_publishing_frequency = session_response.get('findingPublishingFrequency', 'UNKNOWN')
|
|
48
|
+
|
|
49
|
+
return [{
|
|
50
|
+
'MacieSessionId': f"macie-{region}",
|
|
51
|
+
'Status': status,
|
|
52
|
+
'Region': region,
|
|
53
|
+
'AccountId': aws_factory.get_account_info().get('account_id', 'unknown'),
|
|
54
|
+
'FindingPublishingFrequency': finding_publishing_frequency,
|
|
55
|
+
'ServiceRole': session_response.get('serviceRole', ''),
|
|
56
|
+
'CreatedAt': session_response.get('createdAt', '')
|
|
57
|
+
}]
|
|
58
|
+
|
|
59
|
+
except ClientError as e:
|
|
60
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
61
|
+
if error_code in ['ResourceNotFoundException', 'AccessDeniedException']:
|
|
62
|
+
# Macie not enabled or no access
|
|
63
|
+
return [{
|
|
64
|
+
'MacieSessionId': 'none',
|
|
65
|
+
'Status': 'DISABLED',
|
|
66
|
+
'Region': region,
|
|
67
|
+
'AccountId': aws_factory.get_account_info().get('account_id', 'unknown'),
|
|
68
|
+
'FindingPublishingFrequency': 'UNKNOWN'
|
|
69
|
+
}]
|
|
70
|
+
raise
|
|
71
|
+
|
|
72
|
+
except ClientError as e:
|
|
73
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
74
|
+
if error_code == 'AccessDeniedException':
|
|
75
|
+
logger.warning(f"Access denied to Macie in {region}")
|
|
76
|
+
else:
|
|
77
|
+
logger.error(f"Error checking Macie status in {region}: {e}")
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
def _evaluate_resource_compliance(
|
|
81
|
+
self,
|
|
82
|
+
resource: Dict[str, Any],
|
|
83
|
+
aws_factory: AWSClientFactory,
|
|
84
|
+
region: str
|
|
85
|
+
) -> ComplianceResult:
|
|
86
|
+
"""Evaluate if Amazon Macie is enabled."""
|
|
87
|
+
macie_id = resource.get('MacieSessionId', 'none')
|
|
88
|
+
status = resource.get('Status', 'DISABLED')
|
|
89
|
+
finding_frequency = resource.get('FindingPublishingFrequency', 'UNKNOWN')
|
|
90
|
+
|
|
91
|
+
# Check if Macie is enabled
|
|
92
|
+
is_compliant = status == 'ENABLED'
|
|
93
|
+
|
|
94
|
+
if is_compliant:
|
|
95
|
+
evaluation_reason = (
|
|
96
|
+
f"Amazon Macie is enabled in {region}. "
|
|
97
|
+
f"Finding publishing frequency: {finding_frequency}"
|
|
98
|
+
)
|
|
99
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
100
|
+
else:
|
|
101
|
+
if macie_id == 'none' or status == 'DISABLED':
|
|
102
|
+
evaluation_reason = f"Amazon Macie is not enabled in {region}."
|
|
103
|
+
elif status == 'PAUSED':
|
|
104
|
+
evaluation_reason = f"Amazon Macie is paused in {region}."
|
|
105
|
+
else:
|
|
106
|
+
evaluation_reason = f"Amazon Macie status is {status} in {region}."
|
|
107
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
108
|
+
|
|
109
|
+
return ComplianceResult(
|
|
110
|
+
resource_id=macie_id,
|
|
111
|
+
resource_type="AWS::Macie::Session",
|
|
112
|
+
compliance_status=compliance_status,
|
|
113
|
+
evaluation_reason=evaluation_reason,
|
|
114
|
+
config_rule_name=self.rule_name,
|
|
115
|
+
region=region
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
119
|
+
"""Get remediation steps for enabling Amazon Macie."""
|
|
120
|
+
return [
|
|
121
|
+
"1. Enable Amazon Macie in the AWS Console:",
|
|
122
|
+
" - Navigate to Amazon Macie service",
|
|
123
|
+
" - Click 'Get Started' or 'Enable Macie'",
|
|
124
|
+
" - Review service permissions and pricing",
|
|
125
|
+
" - Click 'Enable Macie'",
|
|
126
|
+
"",
|
|
127
|
+
"2. Enable Macie using AWS CLI:",
|
|
128
|
+
" aws macie2 enable-macie \\",
|
|
129
|
+
" --finding-publishing-frequency FIFTEEN_MINUTES \\",
|
|
130
|
+
" --status ENABLED \\",
|
|
131
|
+
" --region <region>",
|
|
132
|
+
"",
|
|
133
|
+
"3. Configure S3 bucket discovery:",
|
|
134
|
+
" - Macie automatically discovers all S3 buckets",
|
|
135
|
+
" - Review bucket inventory in Macie console",
|
|
136
|
+
" - Identify buckets containing sensitive data",
|
|
137
|
+
"",
|
|
138
|
+
"4. Create sensitive data discovery jobs:",
|
|
139
|
+
" - Create classification jobs for high-priority buckets",
|
|
140
|
+
" - Schedule recurring jobs for continuous monitoring",
|
|
141
|
+
" - Use managed data identifiers (PII, credentials, financial data)",
|
|
142
|
+
" - Create custom data identifiers for organization-specific patterns",
|
|
143
|
+
"",
|
|
144
|
+
"5. Configure findings and alerts:",
|
|
145
|
+
" - Review Macie findings in the console",
|
|
146
|
+
" - Create EventBridge rules to route findings to SNS/Slack",
|
|
147
|
+
" - Integrate with Security Hub for centralized findings",
|
|
148
|
+
" - Set up automated remediation for critical findings",
|
|
149
|
+
"",
|
|
150
|
+
"6. Review and act on findings:",
|
|
151
|
+
" - Critical findings: Immediate action required",
|
|
152
|
+
" - High findings: Review within 24 hours",
|
|
153
|
+
" - Medium/Low findings: Review weekly",
|
|
154
|
+
"",
|
|
155
|
+
"7. Optimize costs:",
|
|
156
|
+
" - Macie charges per GB scanned and per bucket monitored",
|
|
157
|
+
" - Focus discovery jobs on high-risk buckets",
|
|
158
|
+
" - Use sampling for large datasets",
|
|
159
|
+
"",
|
|
160
|
+
"Priority: HIGH - Sensitive data discovery is critical for compliance",
|
|
161
|
+
"Effort: Medium - Initial setup is quick, ongoing effort for job configuration",
|
|
162
|
+
"",
|
|
163
|
+
"AWS Documentation:",
|
|
164
|
+
"https://docs.aws.amazon.com/macie/latest/user/what-is-macie.html"
|
|
165
|
+
]
|